@mathscan/geometry-engine 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -7
- package/dist/core.cjs +1 -1
- package/dist/core.mjs +4 -4
- package/dist/entries/core.d.cts +1 -1
- package/dist/entries/core.d.ts +1 -1
- package/dist/entries/core.d.ts.map +1 -1
- package/dist/exercise.schema-Bma3qN9P.js +251 -0
- package/dist/exercise.schema-Bma3qN9P.js.map +1 -0
- package/dist/exercise.schema-Oj5ZH-yO.cjs +2 -0
- package/dist/exercise.schema-Oj5ZH-yO.cjs.map +1 -0
- package/dist/exerciseLoad-Db9Cigxi.cjs +2 -0
- package/dist/exerciseLoad-Db9Cigxi.cjs.map +1 -0
- package/dist/{geometryAttemptStatus-5L_q6-9l.js → exerciseLoad-DgeDXYbf.js} +84 -71
- package/dist/exerciseLoad-DgeDXYbf.js.map +1 -0
- package/dist/geometry/entities/angle.d.cts +56 -0
- package/dist/geometry/entities/angle.d.ts +57 -0
- package/dist/geometry/entities/angle.d.ts.map +1 -0
- package/dist/geometry/entities/circle.d.ts.map +1 -1
- package/dist/geometry/entities/index.d.cts +8 -4
- package/dist/geometry/entities/index.d.ts +8 -4
- package/dist/geometry/entities/index.d.ts.map +1 -1
- package/dist/geometry/entities/line.d.ts.map +1 -1
- package/dist/geometry/entities/point.d.ts.map +1 -1
- package/dist/geometry/entities/relations.d.cts +8 -1
- package/dist/geometry/entities/relations.d.ts +8 -1
- package/dist/geometry/entities/relations.d.ts.map +1 -1
- package/dist/geometry/entities/segment.d.ts.map +1 -1
- package/dist/geometry/entityModel.d.cts +21 -3
- package/dist/geometry/entityModel.d.ts +21 -3
- package/dist/geometry/entityModel.d.ts.map +1 -1
- package/dist/geometryState.schema-B5ccMKDa.cjs +2 -0
- package/dist/geometryState.schema-B5ccMKDa.cjs.map +1 -0
- package/dist/geometryState.schema-CKFPlX8j.js +1177 -0
- package/dist/geometryState.schema-CKFPlX8j.js.map +1 -0
- package/dist/goals-BlSeniyt.js +563 -0
- package/dist/goals-BlSeniyt.js.map +1 -0
- package/dist/goals-DLbmBZD8.cjs +2 -0
- package/dist/goals-DLbmBZD8.cjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +8 -8
- package/dist/measure/angleType.d.cts +2 -0
- package/dist/measure/angleType.d.ts +3 -0
- package/dist/measure/angleType.d.ts.map +1 -0
- package/dist/measure/geometryAdapter.d.cts +3 -0
- package/dist/measure/geometryAdapter.d.ts +3 -0
- package/dist/measure/geometryAdapter.d.ts.map +1 -1
- package/dist/react-B26_e52I.cjs +2 -0
- package/dist/react-B26_e52I.cjs.map +1 -0
- package/dist/{react-BEcdx1f6.js → react-B5cGaxH8.js} +738 -513
- package/dist/react-B5cGaxH8.js.map +1 -0
- package/dist/react.cjs +1 -1
- package/dist/react.mjs +1 -1
- package/dist/renderer/GeometryCanvas.d.ts.map +1 -1
- package/dist/renderer/StudentGeometryCanvas.d.ts.map +1 -1
- package/dist/renderer/Toolbar.d.ts.map +1 -1
- package/dist/renderer/angleMeasure.d.cts +14 -0
- package/dist/renderer/angleMeasure.d.ts +15 -0
- package/dist/renderer/angleMeasure.d.ts.map +1 -0
- package/dist/renderer/deriveRenderableGeometry.d.cts +1 -0
- package/dist/renderer/deriveRenderableGeometry.d.ts +1 -0
- package/dist/renderer/deriveRenderableGeometry.d.ts.map +1 -1
- package/dist/renderer/deriveSemanticScene.d.ts.map +1 -1
- package/dist/renderer/dragSnap.d.cts +14 -0
- package/dist/renderer/dragSnap.d.ts +15 -0
- package/dist/renderer/dragSnap.d.ts.map +1 -0
- package/dist/renderer/shapes/AngleShape.d.cts +19 -0
- package/dist/renderer/shapes/AngleShape.d.ts +20 -0
- package/dist/renderer/shapes/AngleShape.d.ts.map +1 -0
- package/dist/renderer/shapes/EntityLabel.d.cts +9 -1
- package/dist/renderer/shapes/EntityLabel.d.ts +9 -1
- package/dist/renderer/shapes/EntityLabel.d.ts.map +1 -1
- package/dist/renderer/shapes/EntityShape.d.cts +2 -1
- package/dist/renderer/shapes/EntityShape.d.ts +2 -1
- package/dist/renderer/shapes/EntityShape.d.ts.map +1 -1
- package/dist/renderer/useConnectionPreview.d.cts +12 -1
- package/dist/renderer/useConnectionPreview.d.ts +12 -1
- package/dist/renderer/useConnectionPreview.d.ts.map +1 -1
- package/dist/renderer/usePointerGestures.d.cts +1 -1
- package/dist/renderer/usePointerGestures.d.ts +1 -1
- package/dist/renderer/usePointerGestures.d.ts.map +1 -1
- package/dist/schemas/entityDefinition.schema.d.cts +7 -0
- package/dist/schemas/entityDefinition.schema.d.ts +7 -0
- package/dist/schemas/entityDefinition.schema.d.ts.map +1 -1
- package/dist/schemas/entityDocument.d.cts +34 -13
- package/dist/schemas/entityDocument.d.ts +34 -13
- package/dist/schemas/entityDocument.d.ts.map +1 -1
- package/dist/schemas/entityPresentation.schema.d.cts +3 -0
- package/dist/schemas/entityPresentation.schema.d.ts +3 -0
- package/dist/schemas/entityPresentation.schema.d.ts.map +1 -1
- package/dist/schemas/exercise.schema.d.cts +80 -2
- package/dist/schemas/exercise.schema.d.ts +80 -2
- package/dist/schemas/exercise.schema.d.ts.map +1 -1
- package/dist/schemas/exerciseV2.schema.d.cts +57 -0
- package/dist/schemas/exerciseV2.schema.d.ts +57 -0
- package/dist/schemas/exerciseV2.schema.d.ts.map +1 -1
- package/dist/schemas/exerciseV4.schema.d.cts +168 -0
- package/dist/schemas/exerciseV4.schema.d.ts +169 -0
- package/dist/schemas/exerciseV4.schema.d.ts.map +1 -0
- package/dist/schemas/geometryState.schema.d.cts +8 -0
- package/dist/schemas/geometryState.schema.d.ts +8 -0
- package/dist/schemas/geometryState.schema.d.ts.map +1 -1
- package/dist/schemas/goals/angleBisectorGoal.schema.d.cts +15 -0
- package/dist/schemas/goals/angleBisectorGoal.schema.d.ts +16 -0
- package/dist/schemas/goals/angleBisectorGoal.schema.d.ts.map +1 -0
- package/dist/schemas/goals/angleCriterion.schema.d.cts +28 -0
- package/dist/schemas/goals/angleCriterion.schema.d.ts +29 -0
- package/dist/schemas/goals/angleCriterion.schema.d.ts.map +1 -0
- package/dist/schemas/goals/angleMeasureGoal.schema.d.cts +26 -0
- package/dist/schemas/goals/angleMeasureGoal.schema.d.ts +27 -0
- package/dist/schemas/goals/angleMeasureGoal.schema.d.ts.map +1 -0
- package/dist/schemas/goals/createGoal.schema.d.cts +1 -0
- package/dist/schemas/goals/createGoal.schema.d.ts +1 -0
- package/dist/schemas/goals/createGoal.schema.d.ts.map +1 -1
- package/dist/schemas/goals/geometryGoal.schema.d.cts +61 -0
- package/dist/schemas/goals/geometryGoal.schema.d.ts +61 -0
- package/dist/schemas/goals/geometryGoal.schema.d.ts.map +1 -1
- package/dist/schemas/goals/givenAngleGoal.schema.d.cts +35 -0
- package/dist/schemas/goals/givenAngleGoal.schema.d.ts +36 -0
- package/dist/schemas/goals/givenAngleGoal.schema.d.ts.map +1 -0
- package/dist/schemas/goals/goalKindDeclaration.d.cts +7 -0
- package/dist/schemas/goals/goalKindDeclaration.d.ts +7 -0
- package/dist/schemas/goals/goalKindDeclaration.d.ts.map +1 -1
- package/dist/schemas/goals/parallelGoal.schema.d.cts +1 -0
- package/dist/schemas/goals/parallelGoal.schema.d.ts +1 -0
- package/dist/schemas/goals/parallelGoal.schema.d.ts.map +1 -1
- package/dist/schemas/goals/perpendicularGoal.schema.d.cts +1 -0
- package/dist/schemas/goals/perpendicularGoal.schema.d.ts +1 -0
- package/dist/schemas/goals/perpendicularGoal.schema.d.ts.map +1 -1
- package/dist/schemas/goals/throughPointsGoal.schema.d.cts +1 -0
- package/dist/schemas/goals/throughPointsGoal.schema.d.ts +1 -0
- package/dist/schemas/goals/throughPointsGoal.schema.d.ts.map +1 -1
- package/dist/schemas/objectTypeName.schema.d.cts +1 -0
- package/dist/schemas/objectTypeName.schema.d.ts +1 -0
- package/dist/schemas/objectTypeName.schema.d.ts.map +1 -1
- package/dist/schemas/v2ToV3.d.cts +15 -1
- package/dist/schemas/v2ToV3.d.ts +15 -1
- package/dist/schemas/v2ToV3.d.ts.map +1 -1
- package/dist/schemas/v3ToV4.d.cts +2 -1
- package/dist/schemas/v3ToV4.d.ts +2 -1
- package/dist/schemas/v3ToV4.d.ts.map +1 -1
- package/dist/schemas/v4ToV5.d.cts +7 -0
- package/dist/schemas/v4ToV5.d.ts +8 -0
- package/dist/schemas/v4ToV5.d.ts.map +1 -0
- package/dist/schemas.cjs +1 -1
- package/dist/schemas.mjs +3 -3
- package/dist/snapping/computeDragSnap.d.cts +30 -0
- package/dist/snapping/computeDragSnap.d.ts +31 -0
- package/dist/snapping/computeDragSnap.d.ts.map +1 -0
- package/dist/state/geometryContext.d.ts.map +1 -1
- package/dist/store/attemptChange.d.cts +1 -1
- package/dist/store/attemptChange.d.ts +1 -1
- package/dist/store/attemptChange.d.ts.map +1 -1
- package/dist/store/constructionCommand.d.cts +14 -2
- package/dist/store/constructionCommand.d.ts +14 -2
- package/dist/store/constructionCommand.d.ts.map +1 -1
- package/dist/store/constructionIdentity.d.cts +9 -0
- package/dist/store/constructionIdentity.d.ts +9 -0
- package/dist/store/constructionIdentity.d.ts.map +1 -1
- package/dist/store/studentStore.d.cts +7 -0
- package/dist/store/studentStore.d.ts +7 -0
- package/dist/store/studentStore.d.ts.map +1 -1
- package/dist/store/teacherStore.d.cts +16 -0
- package/dist/store/teacherStore.d.ts +16 -0
- package/dist/store/teacherStore.d.ts.map +1 -1
- package/dist/{studentStore-CEKTtMCh.js → studentStore-CyYa0N93.js} +502 -370
- package/dist/studentStore-CyYa0N93.js.map +1 -0
- package/dist/studentStore-ubldy4PJ.cjs +2 -0
- package/dist/studentStore-ubldy4PJ.cjs.map +1 -0
- package/dist/validation/getGeometryExerciseWarnings.d.cts +3 -2
- package/dist/validation/getGeometryExerciseWarnings.d.ts +3 -2
- package/dist/validation/getGeometryExerciseWarnings.d.ts.map +1 -1
- package/dist/validation/goals/angleBisector.d.cts +3 -0
- package/dist/validation/goals/angleBisector.d.ts +4 -0
- package/dist/validation/goals/angleBisector.d.ts.map +1 -0
- package/dist/validation/goals/angleCriterion.d.cts +12 -0
- package/dist/validation/goals/angleCriterion.d.ts +13 -0
- package/dist/validation/goals/angleCriterion.d.ts.map +1 -0
- package/dist/validation/goals/angleMeasure.d.cts +9 -0
- package/dist/validation/goals/angleMeasure.d.ts +10 -0
- package/dist/validation/goals/angleMeasure.d.ts.map +1 -0
- package/dist/validation/goals/create.d.cts +2 -2
- package/dist/validation/goals/create.d.ts +2 -2
- package/dist/validation/goals/create.d.ts.map +1 -1
- package/dist/validation/goals/givenAngle.d.cts +11 -0
- package/dist/validation/goals/givenAngle.d.ts +12 -0
- package/dist/validation/goals/givenAngle.d.ts.map +1 -0
- package/dist/validation/goals/goalKindHandler.d.cts +38 -1
- package/dist/validation/goals/goalKindHandler.d.ts +38 -1
- package/dist/validation/goals/goalKindHandler.d.ts.map +1 -1
- package/dist/validation/goals/index.d.cts +9 -2
- package/dist/validation/goals/index.d.ts +9 -2
- package/dist/validation/goals/index.d.ts.map +1 -1
- package/dist/validation/goals/midpoint.d.cts +2 -2
- package/dist/validation/goals/midpoint.d.ts +2 -2
- package/dist/validation/goals/midpoint.d.ts.map +1 -1
- package/dist/validation/goals/parallel.d.cts +2 -2
- package/dist/validation/goals/parallel.d.ts +2 -2
- package/dist/validation/goals/parallel.d.ts.map +1 -1
- package/dist/validation/goals/perpendicular.d.cts +2 -2
- package/dist/validation/goals/perpendicular.d.ts +2 -2
- package/dist/validation/goals/perpendicular.d.ts.map +1 -1
- package/dist/validation/goals/segmentLength.d.cts +2 -2
- package/dist/validation/goals/segmentLength.d.ts +2 -2
- package/dist/validation/goals/segmentLength.d.ts.map +1 -1
- package/dist/validation/goals/throughPoints.d.cts +2 -2
- package/dist/validation/goals/throughPoints.d.ts +2 -2
- package/dist/validation/goals/throughPoints.d.ts.map +1 -1
- package/dist/validation/types.d.cts +5 -1
- package/dist/validation/types.d.ts +5 -1
- package/dist/validation/types.d.ts.map +1 -1
- package/dist/validation/validateGeometryState.d.ts.map +1 -1
- package/dist/validation/validateGeometryStateInput.d.ts.map +1 -1
- package/dist/validation.cjs +1 -1
- package/dist/validation.cjs.map +1 -1
- package/dist/validation.mjs +106 -85
- package/dist/validation.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/exercise.schema-BV2b7rtN.cjs +0 -2
- package/dist/exercise.schema-BV2b7rtN.cjs.map +0 -1
- package/dist/exercise.schema-ckVBW9XA.js +0 -212
- package/dist/exercise.schema-ckVBW9XA.js.map +0 -1
- package/dist/geometryAttemptStatus-5L_q6-9l.js.map +0 -1
- package/dist/geometryAttemptStatus-N_XHIkiS.cjs +0 -2
- package/dist/geometryAttemptStatus-N_XHIkiS.cjs.map +0 -1
- package/dist/geometryState.schema-D0ixyGYE.js +0 -955
- package/dist/geometryState.schema-D0ixyGYE.js.map +0 -1
- package/dist/geometryState.schema-DKQ-durx.cjs +0 -2
- package/dist/geometryState.schema-DKQ-durx.cjs.map +0 -1
- package/dist/goals-C3ZwbEh_.js +0 -451
- package/dist/goals-C3ZwbEh_.js.map +0 -1
- package/dist/goals-CxR5qPDC.cjs +0 -2
- package/dist/goals-CxR5qPDC.cjs.map +0 -1
- package/dist/react-BEcdx1f6.js.map +0 -1
- package/dist/react-Pp_VSk--.cjs +0 -2
- package/dist/react-Pp_VSk--.cjs.map +0 -1
- package/dist/studentStore-CEKTtMCh.js.map +0 -1
- package/dist/studentStore-DLftfFAl.cjs +0 -2
- package/dist/studentStore-DLftfFAl.cjs.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"react-Pp_VSk--.cjs","names":[],"sources":["../src/geometry/entityCapabilities.ts","../src/renderer/deriveRenderableGeometry.ts","../src/renderer/useConnectionPreview.ts","../src/renderer/usePlacementSnap.ts","../src/renderer/gesturePolicy.ts","../src/renderer/usePointerGestures.ts","../src/renderer/shapes/SnapMarker.tsx","../src/renderer/shapes/PointShape.tsx","../src/renderer/shapes/StrokedLine.tsx","../src/renderer/shapes/SegmentShape.tsx","../src/renderer/shapes/LineShape.tsx","../src/renderer/shapes/CircleShape.tsx","../src/renderer/shapes/EntityLabel.tsx","../src/renderer/shapes/EntityShape.tsx","../src/renderer/GeometryGrid.tsx","../src/renderer/GeometryInstruments.tsx","../src/store/useStoreState.ts","../src/renderer/GeometryCanvas.tsx","../src/renderer/entityDisplayName.ts","../src/renderer/StudentGeometryCanvas.tsx","../src/renderer/deriveSemanticScene.ts","../src/renderer/GeometryCompanion.tsx","../src/renderer/studentExerciseSession.ts","../src/renderer/Toolbar.tsx","../src/renderer/StudentGeometryExercise.tsx"],"sourcesContent":["// The capability triple, derived from an entity's role and never persisted\n// (ADR 0008 § Presentation, RFC 0001 § Q11). This is the \"one registry\n// function\" the ADR names: what a canvas draws, what a click may select and\n// what a pointer may snap to are three readings of one table, so no consumer\n// can answer the same question differently.\n//\n// A mapped type over `EntityRole`, exhaustive the way `EntityRegistry` and the\n// shape table are: a role added to the union is a compile error here until it\n// says what it can do.\nimport type { EntityCapabilities, EntityRole } from './entityModel'\n\n// `snapTarget` is true for both roles, and it is the row ADR 0008 calls\n// load-bearing: an implicit entity is not drawn and cannot be selected, but a\n// construction must still be able to land on it — that is what makes two shapes\n// share a vertex instead of stacking two points at one coordinate and only\n// looking connected.\nconst capabilityTable: { readonly [R in EntityRole]: Readonly<EntityCapabilities> } = {\n implicit: { rendered: false, selectable: false, snapTarget: true },\n explicit: { rendered: true, selectable: true, snapTarget: true },\n}\n\n// One shared record per role rather than a fresh object: this is asked once per\n// entity while a figure is drawn, and the answer depends on nothing else.\nexport function capabilitiesOf(role: EntityRole): Readonly<EntityCapabilities> {\n return capabilityTable[role]\n}\n","import { capabilitiesOf } from '../geometry/entityCapabilities'\nimport type { EntityCapabilities, EntityId, GeometryContext } from '../geometry/entityModel'\nimport type { SceneEntity } from '../geometry/hitTest'\nimport type { Point, PointId } from '../geometry/point'\nimport type { GeometryExercise } from '../schemas/exercise.schema'\nimport type { GeometryState } from '../schemas/geometryState.schema'\nimport { buildGeometryContext } from '../state/geometryContext'\nimport { exerciseGeometryContext, objectsInScope } from '../state/resolvePoints'\nimport type { TeacherEditorState } from '../store/teacherStore'\n\n// Normalization point so shape components stay uniformly map-driven,\n// instead of GeometryCanvas branching on shape kinds while rendering.\n//\n// **One object list, not one array per kind** (M2.8). It held `segments` and\n// `lines` until a circle arrived and made the third array obvious: what the\n// canvas needs is the points to resolve against and the objects to draw, and\n// which kind each object is belongs to the registry that draws it.\n//\n// **Points are resolved, not read off the document** (M2.6.c.1). A v1 given\n// point carried its own coordinates and there was nothing to resolve; a v2\n// exercise entity is a definition like any other, so a hand-authored given\n// sitting on a given segment has coordinates only once the graph has been\n// walked.\n//\n// **The scene is the whole scene, and `capabilitiesOf` says what may be done\n// with each part of it** (M2.G.4). `points` and `objects` stay complete because\n// they are also the snap scene, and an implicit entity is still a snap target;\n// a canvas drops what it may not draw as it iterates, rather than being handed\n// a pre-filtered list that would silently un-snap it.\nexport interface RenderableGeometry {\n points: ReadonlyMap<PointId, Point>\n objects: SceneEntity[]\n // A closure over the context, not a method: the canvases destructure it, and\n // the two documents' presentation defaults are the context's to know.\n capabilitiesOf: (id: EntityId) => Readonly<EntityCapabilities>\n // The authored `label`, for the entity a canvas is about to draw (M2.G.4.b).\n // Read here for the same reason the capabilities are: it is the same\n // `presentation` entry, and the two documents' defaults are the context's to\n // know. `undefined` for an entity with no authored label, and for an implicit\n // one — a label is only meaningful on an explicit entity (ADR 0008 §\n // Presentation), and the fallback text a canvas draws instead is that\n // canvas's own convention, not the document's.\n labelOf: (id: EntityId) => string | undefined\n // The `snapTarget` row, in the shape snapping consumes. Handed to\n // `computeSnap()` rather than applied to `points` and `objects` here: the\n // scene is deliberately the whole document, and pre-filtering it would make\n // \"not a snap target\" and \"not in the scene\" the same thing again.\n isSnapTarget: (id: EntityId) => boolean\n}\n\n// Teacher-mode normalization: an exercise being authored has no attempt, so\n// the scene is the exercise's own entities resolved against nothing else.\nexport function deriveRenderableGeometry(state: TeacherEditorState): RenderableGeometry {\n const context = exerciseGeometryContext(state.exercise)\n // Walked in declaration order, not in resolution order: the canvas draws in\n // the order it iterates, and an author reordering the entity record expects\n // the figure to follow. The context answers *where* each point is; the record\n // says in which order they were written.\n const resolved = context.points\n const points = new Map<PointId, Point>()\n const objects: SceneEntity[] = []\n\n for (const [id, definition] of Object.entries(state.exercise.entities)) {\n if (definition.kind !== 'point') {\n objects.push({ id, definition })\n continue\n }\n // A point the graph could not place has no coordinates and so draws\n // nothing, exactly as an unresolvable object does.\n const at = resolved.get(id)\n if (at) points.set(id, at)\n }\n\n return renderableOf(points, objects, context)\n}\n\n// Student-mode normalization, and the reason this is a function rather than\n// three statements inside `StudentGeometryCanvas`: the canvas cannot be\n// rendered in a unit test — it is all hooks and a Konva stage — so what it\n// draws is only assertable if the decision is taken out here.\n//\n// Points arrive in resolution order (pinned givens, draggable givens, then the\n// attempt's own), which is the order the student canvas has always drawn them\n// in and the order `findPointAt()` depends on.\nexport function deriveStudentRenderableGeometry(\n exercise: GeometryExercise,\n geometry: GeometryState,\n): RenderableGeometry {\n const context = buildGeometryContext(exercise, geometry)\n // Givens first, so a construction re-declaring a given id loses to it exactly\n // as it does in the context.\n return renderableOf(context.points, [...objectsInScope(exercise, geometry).values()], context)\n}\n\n// The two documents' presentation defaults differ (ADR 0008) and the context is\n// the only thing that knows both, so the role is read there and turned into\n// capabilities by the one registry function. Every row a consumer reads is\n// derived here, so no canvas re-derives one of the three from a role of its own.\nfunction renderableOf(\n points: ReadonlyMap<PointId, Point>,\n objects: SceneEntity[],\n context: GeometryContext,\n): RenderableGeometry {\n const of = (id: EntityId) => capabilitiesOf(context.presentationOf(id).role)\n const labelOf = (id: EntityId) => {\n const presentation = context.presentationOf(id)\n return presentation.role === 'explicit' ? presentation.label : undefined\n }\n return {\n points,\n objects,\n capabilitiesOf: of,\n labelOf,\n isSnapTarget: (id) => of(id).snapTarget,\n }\n}\n","import { useRef, useState } from 'react'\nimport {\n computePlacement,\n mathToScreen,\n screenLengthToMath,\n screenToMath,\n type CanvasSize,\n} from '../coordinates/transform'\nimport { clipLineToViewport } from '../coordinates/lineClipping'\nimport type { SceneEntity } from '../geometry/hitTest'\nimport type { Point, PointId } from '../geometry/point'\nimport type { ConstructionEndpoint } from '../store/constructionCommand'\nimport { CONNECTION_SNAP_RADIUS_PX } from '../numerics/policy'\nimport { computeSnap, originForSnap, type SnapResult } from '../snapping/computeSnap'\nimport type { MathViewport } from '../schemas/viewport.schema'\nimport type { ObjectTypeName } from '../schemas/objectTypeName.schema'\nimport type { ScreenPoint } from './gesturePolicy'\n\ninterface UseConnectionPreviewArgs {\n points: ReadonlyMap<PointId, Point>\n viewport: MathViewport\n canvas: CanvasSize\n pendingObjectType: ObjectTypeName | null\n pendingFirstPointId: PointId | null\n // Both ends of a completed drag, reported once. The hook resolves *where*\n // the gesture landed; what that costs — reusing a point, inventing one, how\n // many undo entries it leaves — is the command's decision, and the preview\n // never writes to the store at all (RFC 0001 § 11).\n connect: (from: ConstructionEndpoint, to: ConstructionEndpoint) => void\n // A tap on an existing endpoint: the store's click-click action, which\n // remembers a first endpoint, forgets it when it is tapped again, and builds\n // on a second one.\n pick: (id: PointId) => void\n // Objects the gesture may end on. Without these only existing points are\n // offered.\n objects?: readonly SceneEntity[]\n // Which entities the gesture may snap to — `capabilitiesOf(id).snapTarget`.\n // It governs both ends of a connection, not only the snapped one: a point the\n // gesture starts from and a point Konva reports under the release are\n // endpoints just as much as a computed snap is, and each of them reaches this\n // hook by a path that never passes through `computeSnap()`.\n isSnapTarget?: (id: PointId) => boolean\n // Whether a gesture may end where no point exists yet. Both built-in\n // canvases enable this; keeping it explicit lets narrower hosts reuse the\n // hook without permitting point creation.\n canCreatePoints?: boolean\n // Student endpoints remember an on-object or intersection origin so they\n // follow their parents. Teacher authoring deliberately stores a free point\n // at the snapped position: M2.G allows derived givens in loaded documents,\n // but leaves an editor affordance for authoring them to M3.\n preserveCreatedPointOrigins?: boolean\n}\n\n// One contact's connection, from press to release. Held in a ref, not in\n// state: a release can arrive in the same task as the press that opened it,\n// before React has rendered once, and it must see what the press resolved.\ninterface ConnectionGesture {\n // The start is an endpoint like the end is: a gesture may begin on bare\n // canvas, where there is no point yet to name (RFC 0001 § 11).\n from: ConstructionEndpoint\n // Where the preview is drawn from: the point for an existing endpoint,\n // otherwise where the gesture went down.\n fromPoint: Point\n // The first endpoint came from an earlier tap, so this contact only chooses\n // the second one.\n completing: boolean\n // Where the preview is drawn to. Null until the contact crosses its drag\n // threshold — below it nothing is being dragged, so nothing is previewed.\n pointer: ScreenPoint | null\n}\n\n// What the canvas draws, published from the refs after every change. Render\n// reads this and never the refs: the refs are the gesture, this is its picture.\ninterface PreviewView {\n // The gesture's start, or null when only the mouse is hovering from a tapped\n // first endpoint — which the render reads from the store instead.\n gestureFrom: Point | null\n pointer: ScreenPoint | null\n snap: SnapResult | null\n}\n\nconst NO_PREVIEW: PreviewView = { gestureFrom: null, pointer: null, snap: null }\n\n/**\n * The shared \"tap two points, or drag from one to another\" gesture behind\n * building a segment or a line, used identically by the teacher and student\n * canvases so the two roles can never drift out of visual or interaction sync.\n *\n * It resolves endpoints and draws the preview; it does not listen to a single\n * event. `usePointerGestures` owns the pointer lifecycle and calls in with\n * `press`, `drag`, `release` and `cancel`, already classified against the drag\n * threshold (RFC 0004 § Decision 4).\n */\nexport function useConnectionPreview({\n points,\n viewport,\n canvas,\n pendingObjectType,\n pendingFirstPointId,\n connect,\n pick,\n objects = [],\n isSnapTarget,\n canCreatePoints = false,\n preserveCreatedPointOrigins = true,\n}: UseConnectionPreviewArgs) {\n const gesture = useRef<ConnectionGesture | null>(null)\n // The mouse's preview from a tapped first endpoint, before any button is\n // down. A finger has no hover, so this only ever follows a mouse or a pen.\n const hoverPointer = useRef<ScreenPoint | null>(null)\n const snap = useRef<SnapResult | null>(null)\n const [view, setView] = useState<PreviewView>(NO_PREVIEW)\n const connecting = pendingObjectType === 'segment' || pendingObjectType === 'line'\n // A finger-sized target, converted from pixels once: hit testing itself is\n // math space only.\n const placement = computePlacement(viewport, canvas)\n const snapTolerance = screenLengthToMath(CONNECTION_SNAP_RADIUS_PX, placement)\n const scene = { points, objects, viewport, isSnapTarget }\n const snapTargetId = view.snap?.kind === 'point' ? (view.snap.targetIds[0] ?? null) : null\n\n function redraw(): void {\n const active = gesture.current\n setView(\n active\n ? { gestureFrom: active.fromPoint, pointer: active.pointer, snap: snap.current }\n : { gestureFrom: null, pointer: hoverPointer.current, snap: snap.current },\n )\n }\n\n function toMath(pointer: ScreenPoint): Point {\n return screenToMath(pointer, viewport, canvas, placement)\n }\n\n function acceptsEndpoint(id: PointId): boolean {\n return points.has(id) && (!isSnapTarget || isSnapTarget(id))\n }\n\n // Grid crossings are left out on purpose: while dragging a connection they\n // are far more likely to catch the gesture by accident than to be aimed at,\n // and unlike a point or an intersection they mark nothing. A point the\n // gesture began on cannot also end it; a start that is not a point yet\n // excludes nothing — there is no id to name.\n function snapAt(pointer: ScreenPoint, from: ConstructionEndpoint | null): SnapResult | null {\n return computeSnap(toMath(pointer), scene, {\n tolerance: snapTolerance,\n exclude: from?.kind === 'existing' ? [from.id] : [],\n kinds: canCreatePoints ? ['point', 'intersection', 'onObject'] : ['point'],\n })\n }\n\n const previewPoints = (() => {\n const from =\n view.gestureFrom ?? (pendingFirstPointId ? (points.get(pendingFirstPointId) ?? null) : null)\n const { pointer } = view\n if (!connecting || !from || !pointer) return null\n // The preview follows the snap, so the line visibly commits to the target\n // before the student releases.\n const previewPointer = view.snap ? mathToScreen(view.snap.position, viewport, canvas) : pointer\n if (pendingObjectType !== 'line') {\n const start = mathToScreen(from, viewport, canvas)\n return [start.x, start.y, previewPointer.x, previewPointer.y]\n }\n const clipped = clipLineToViewport(from, toMath(previewPointer), viewport)\n if (!clipped) return null\n const start = mathToScreen(clipped[0], viewport, canvas)\n const end = mathToScreen(clipped[1], viewport, canvas)\n return [start.x, start.y, end.x, end.y]\n })()\n\n /**\n * A contact went down. `pressedPointId` is the drawn point under it, if any.\n *\n * A press on bare canvas is resolved by the same `computeSnap()` a release\n * uses, so a gesture beginning on a crossing records `{ intersectionOf }`\n * exactly as one ending there does. Nothing is written: the endpoint is only\n * described, and a gesture abandoned before release leaves the document\n * untouched.\n */\n function press(pointer: ScreenPoint, pressedPointId: PointId | null): void {\n gesture.current = null\n hoverPointer.current = null\n snap.current = null\n if (!connecting) return\n if (pendingFirstPointId) {\n const from = { kind: 'existing', id: pendingFirstPointId } as const\n gesture.current = {\n from,\n fromPoint: points.get(pendingFirstPointId) ?? toMath(pointer),\n completing: true,\n pointer,\n }\n snap.current = snapAt(pointer, from)\n redraw()\n return\n }\n // Pressing a point is the shortest path to an endpoint and the one that\n // asks nothing — but it asks the row: an entity that is not a snap target\n // is not an endpoint by any route. A press beside one snaps to it.\n let id = pressedPointId && acceptsEndpoint(pressedPointId) ? pressedPointId : null\n const position = toMath(pointer)\n let snapped: SnapResult | null = null\n if (!id) {\n if (!canCreatePoints) return\n snapped = computeSnap(position, scene, {\n tolerance: snapTolerance,\n kinds: ['point', 'intersection', 'onObject'],\n })\n if (snapped?.kind === 'point') {\n id = snapped.targetIds[0] ?? null\n if (!id || !acceptsEndpoint(id)) return\n }\n }\n if (id) {\n gesture.current = {\n from: { kind: 'existing', id },\n fromPoint: points.get(id)!,\n completing: false,\n pointer: null,\n }\n return\n }\n const at = snapped?.position ?? position\n const origin =\n snapped && preserveCreatedPointOrigins ? originForSnap({ position: at, snap: snapped }) : null\n gesture.current = {\n from: { kind: 'create', origin: origin ?? { kind: 'point', at } },\n fromPoint: at,\n completing: false,\n pointer: null,\n }\n }\n\n /** The contact moved, past its drag threshold. */\n function drag(pointer: ScreenPoint): void {\n const active = gesture.current\n if (!active) return\n snap.current = snapAt(pointer, active.from)\n gesture.current = { ...active, pointer }\n redraw()\n }\n\n /** A mouse or pen moved with nothing pressed; null once it leaves the canvas. */\n function hover(pointer: ScreenPoint | null): void {\n if (gesture.current) return\n if (!connecting || !pendingFirstPointId || !pointer) {\n if (!hoverPointer.current && !snap.current) return\n hoverPointer.current = null\n snap.current = null\n redraw()\n return\n }\n hoverPointer.current = pointer\n snap.current = snapAt(pointer, { kind: 'existing', id: pendingFirstPointId })\n redraw()\n }\n\n // What a drag ended on — resolved, but not yet built. An intersection or\n // on-object snap names a point that does not exist yet, and this reports the\n // origin rather than creating one: only the command knows whether the\n // gesture completed.\n function dropTarget(\n releasedOnId: PointId | null,\n releasedAt: Point,\n landed: SnapResult | null,\n ): ConstructionEndpoint | null {\n // A canvas that lets a gesture *start* on bare canvas has to let it end\n // there too, so the last fallback is a free point at the release\n // (RFC 0001 § 11).\n const fallback: ConstructionEndpoint | null = releasedOnId\n ? { kind: 'existing', id: releasedOnId }\n : canCreatePoints\n ? { kind: 'create', origin: { kind: 'point', at: releasedAt } }\n : null\n if (!landed) return fallback\n if (landed.kind === 'point') {\n const id = landed.targetIds[0]\n return id ? { kind: 'existing', id } : fallback\n }\n if (!canCreatePoints) return fallback\n if (!preserveCreatedPointOrigins) {\n return { kind: 'create', origin: { kind: 'point', at: landed.position } }\n }\n const origin = originForSnap({ position: landed.position, snap: landed })\n return origin ? { kind: 'create', origin } : fallback\n }\n\n /**\n * The contact lifted normally, on the canvas. `releasedOnPointId` is the drawn\n * point under the release: Konva's own hit, which wins over the computed\n * snap and so is filtered by the same row rather than trusted because it was\n * drawn.\n *\n * Returns whether the contact did anything, so the canvas can treat a tap\n * this ignored as the tap on bare canvas it was.\n */\n function release(\n pointer: ScreenPoint,\n releasedOnPointId: PointId | null,\n dragging: boolean,\n ): boolean {\n const active = gesture.current\n gesture.current = null\n snap.current = null\n redraw()\n if (!active || !connecting) return false\n const releasedOn =\n releasedOnPointId && acceptsEndpoint(releasedOnPointId) ? releasedOnPointId : null\n if (!dragging) {\n // A tap never builds from nothing. It chooses an existing endpoint — the\n // first, or the second of a click-click construction — or it does\n // nothing, so an accidental touch cannot leave invented points behind.\n const tapped = active.completing\n ? (releasedOn ?? snappedPointAt(pointer))\n : active.from.kind === 'existing'\n ? active.from.id\n : null\n if (!tapped) return false\n pick(tapped)\n return true\n }\n // One call, whatever the gesture had to invent: the whole construction is\n // the command's single commit, so a segment that ended on an intersection\n // costs the student one undo and not two. Dragging back onto the start is\n // the store's degeneracy guard to refuse, not a rule restated here.\n const releasedAt = toMath(pointer)\n const target = dropTarget(releasedOn, releasedAt, snapAt(pointer, active.from))\n if (target) connect(active.from, target)\n return true\n }\n\n function snappedPointAt(pointer: ScreenPoint): PointId | null {\n const landed = computeSnap(toMath(pointer), scene, {\n tolerance: snapTolerance,\n kinds: ['point'],\n })\n const id = landed?.kind === 'point' ? landed.targetIds[0] : undefined\n return id && acceptsEndpoint(id) ? id : null\n }\n\n /** Drops the gesture and its preview without building anything. */\n function cancel(): void {\n if (!gesture.current && !hoverPointer.current && !snap.current) return\n gesture.current = null\n hoverPointer.current = null\n snap.current = null\n redraw()\n }\n\n return {\n connecting,\n previewPoints,\n snapTargetId: connecting ? snapTargetId : null,\n // A snap that is not an existing point has nothing on canvas to highlight,\n // so the caller draws a marker at these coordinates instead.\n snapPreview: connecting && view.snap && view.snap.kind !== 'point' ? view.snap : null,\n press,\n drag,\n hover,\n release,\n cancel,\n }\n}\n\nexport type ConnectionPreview = ReturnType<typeof useConnectionPreview>\n","import { useState } from 'react'\nimport type { KonvaEventObject } from 'konva/lib/Node'\nimport {\n computePlacement,\n screenLengthToMath,\n screenToMath,\n type CanvasSize,\n} from '../coordinates/transform'\nimport type { SceneEntity } from '../geometry/hitTest'\nimport type { EntityId } from '../geometry/entityModel'\nimport type { Point, PointId } from '../geometry/point'\nimport { PLACEMENT_SNAP_RADIUS_PX } from '../numerics/policy'\nimport type { MathViewport } from '../schemas/viewport.schema'\nimport { computeSnap, type SnapResult } from '../snapping/computeSnap'\n\n// Where a click lands, and what it landed on — callers that only need\n// coordinates read `position`, while the student store maps the whole thing to\n// a point origin.\nexport interface SnapPlacement {\n position: Point\n snap: SnapResult | null\n}\n\ninterface UsePlacementSnapArgs {\n points: ReadonlyMap<PointId, Point>\n objects: readonly SceneEntity[]\n viewport: MathViewport\n canvas: CanvasSize\n showGrid?: boolean\n // Which entities a placement may land on — `capabilitiesOf(id).snapTarget`.\n // The scene is the whole document because an implicit entity is invisible and\n // still snappable, so the predicate, not the scene, is what says no.\n isSnapTarget?: (id: EntityId) => boolean\n // Only true while the point tool is armed; otherwise no snapping is\n // computed and no marker is drawn.\n active: boolean\n}\n\n// Placement-time snapping for both canvases: tracks the pointer to show where\n// a click would land, and resolves that same position when the click happens.\nexport function usePlacementSnap({\n points,\n objects,\n viewport,\n canvas,\n showGrid,\n isSnapTarget,\n active,\n}: UsePlacementSnapArgs) {\n const [snap, setSnap] = useState<SnapResult | null>(null)\n const placement = computePlacement(viewport, canvas)\n const scene = { points, objects, viewport, showGrid, isSnapTarget }\n const options = { tolerance: screenLengthToMath(PLACEMENT_SNAP_RADIUS_PX, placement) }\n\n // Where the pointer is now, in canvas pixels — what both built-in canvases\n // call from their shared pointer lifecycle, for a hovering mouse and for a\n // finger that is down.\n function trackAt(pointer: Point) {\n if (!active) {\n if (snap) setSnap(null)\n return\n }\n setSnap(computeSnap(screenToMath(pointer, viewport, canvas, placement), scene, options))\n }\n\n // The same, read off a Konva event, for a host that wires its own stage.\n function trackPointer(event: KonvaEventObject<MouseEvent | TouchEvent | PointerEvent>) {\n const pointer = event.target.getStage()?.getPointerPosition()\n if (pointer) trackAt(pointer)\n else if (!active && snap) setSnap(null)\n }\n\n function clearPointer() {\n if (snap) setSnap(null)\n }\n\n // Resolves a click's screen position to the coordinates to place at. Recomputed\n // rather than reusing the tracked snap: a click can arrive without a preceding\n // move (touch, synthetic events), and placement must never be stale.\n function resolvePlacement(pointer: Point): SnapPlacement {\n const at = screenToMath(pointer, viewport, canvas, placement)\n if (!active) return { position: at, snap: null }\n const resolvedSnap = computeSnap(at, scene, options)\n return { position: resolvedSnap?.position ?? at, snap: resolvedSnap }\n }\n\n return { snap: active ? snap : null, trackAt, trackPointer, clearPointer, resolvePlacement }\n}\n","import type { CanvasSize } from '../coordinates/transform'\n\n// The one gesture policy both canvases share (RFC 0004 § Decision 4). Pure\n// arithmetic over CSS pixels: no DOM, no Konva, no store. The lifecycle that\n// feeds it pointer events is `usePointerGestures`; what a classified gesture\n// *does* is each canvas's business.\n//\n// Movement is measured in screen space before any snapping, so a gesture means\n// the same thing at every zoom and every canvas size — which is why a math-space\n// epsilon was rejected as slop.\n\n/**\n * A position within the canvas, in the stage's own pixels from its top-left\n * corner — the frame Konva draws and hit-tests in, and the one `screenToMath`\n * converts from.\n */\nexport interface ScreenPoint {\n x: number\n y: number\n}\n\n/**\n * A pointer's viewport position, in the stage's own pixels. The stage may be\n * displayed at another size than it is laid out at — a CSS transform on a host\n * element, `zoom` on the page — so the offset into its displayed box is divided\n * by the ratio between the two, which is exactly the conversion Konva applies to\n * its own events. Without it, a stage displayed at half size would take every\n * press at twice its distance from the corner.\n */\nexport function toStagePoint(\n client: { x: number; y: number },\n displayed: { left: number; top: number; width: number; height: number },\n laidOut: { width: number; height: number },\n): ScreenPoint {\n const scaleX = laidOut.width > 0 ? displayed.width / laidOut.width || 1 : 1\n const scaleY = laidOut.height > 0 ? displayed.height / laidOut.height || 1 : 1\n return { x: (client.x - displayed.left) / scaleX, y: (client.y - displayed.top) / scaleY }\n}\n\n// A mouse or a pen is precise, and 4 px is where a deliberate drag stops\n// looking like the hand's own tremor. A finger lands with a contact patch and\n// rolls as it lifts, so it gets twice that before a tap turns into a drag.\nexport const MOUSE_DRAG_THRESHOLD_PX = 4\nexport const TOUCH_DRAG_THRESHOLD_PX = 8\n\n/**\n * The displacement at which a contact of this pointer type becomes a drag. An\n * unknown or empty `pointerType` is treated as a mouse: the tighter threshold\n * can only turn a sloppy tap into a harmless drag, never the other way round.\n */\nexport function dragThresholdPx(pointerType: string): number {\n return pointerType === 'touch' ? TOUCH_DRAG_THRESHOLD_PX : MOUSE_DRAG_THRESHOLD_PX\n}\n\n/**\n * One pointer's contact with the canvas, from press to release.\n *\n * Measured in the viewport's CSS pixels — where the hand actually moved — and\n * not in the stage's: a stage displayed at half size must not make a finger\n * drag after 4 px of travel. `maxDisplacement` is the furthest the pointer has\n * been from where it went down, not where it is now: a finger that wanders out\n * and back has still dragged, and releasing where it started must not turn it\n * back into a tap.\n */\nexport interface PointerContact {\n pointerId: number\n pointerType: string\n origin: ScreenPoint\n maxDisplacement: number\n dragging: boolean\n}\n\nexport function beginContact(\n pointerId: number,\n pointerType: string,\n origin: ScreenPoint,\n): PointerContact {\n return { pointerId, pointerType, origin, maxDisplacement: 0, dragging: false }\n}\n\n/** The contact after the pointer reached `at`. At the threshold it is a drag. */\nexport function advanceContact(contact: PointerContact, at: ScreenPoint): PointerContact {\n const displacement = Math.hypot(at.x - contact.origin.x, at.y - contact.origin.y)\n const maxDisplacement = Math.max(contact.maxDisplacement, displacement)\n return {\n ...contact,\n maxDisplacement,\n dragging: contact.dragging || maxDisplacement >= dragThresholdPx(contact.pointerType),\n }\n}\n\n/**\n * Whether a press may open a contact at all. Only a primary pointer does — a\n * second finger is a cancellation, never a gesture of its own — and a mouse\n * only with its main button, so a right-click or a middle-click builds nothing.\n */\nexport function opensContact(press: {\n isPrimary: boolean\n pointerType: string\n button: number\n}): boolean {\n if (!press.isPrimary) return false\n return press.pointerType !== 'mouse' || press.button === 0\n}\n\n/**\n * The stage container's style on both canvases. Every contact on the stage is\n * the lifecycle's: a browser that pans, zooms or selects text under a finger\n * that is drawing takes the pointer with `pointercancel`, and the gesture is\n * lost to scrolling the page.\n */\nexport const GESTURE_SURFACE_STYLE = {\n touchAction: 'none',\n userSelect: 'none',\n WebkitUserSelect: 'none',\n} as const\n\n/** Whether a release landed on the canvas. Anywhere else cancels the gesture. */\nexport function isWithinCanvas(point: ScreenPoint, canvas: CanvasSize): boolean {\n return point.x >= 0 && point.y >= 0 && point.x <= canvas.width && point.y <= canvas.height\n}\n\n/**\n * Why a gesture ended without completing. Every one of them clears the\n * preview, the snap, the pending endpoint and the contact; only `escape` also\n * disarms the tool.\n */\nexport type GestureCancelReason =\n | 'pointercancel'\n | 'second-pointer'\n | 'lost-capture'\n | 'outside-release'\n | 'blur'\n | 'hidden'\n | 'escape'\n | 'invalidated'\n | 'resize'\n | 'store-replaced'\n | 'unmount'\n\n// The Konva node ids a press is classified by. `PointShape` and the object\n// shapes stamp them, and they are how the lifecycle tells a point from an\n// object on Konva's hit canvas. Anything else that listens — an instrument's\n// handle — is not geometry, and a press on it is left to the instrument.\nconst POINT_NODE_PREFIX = 'geometry-point-'\nconst OBJECT_NODE_PREFIX = 'geometry-object-'\n\nexport function pointNodeId(id: string): string {\n return `${POINT_NODE_PREFIX}${id}`\n}\n\nexport function objectNodeId(id: string): string {\n return `${OBJECT_NODE_PREFIX}${id}`\n}\n\n/** What a hit node's id names: a point, an object, or nothing of the figure's. */\nexport function nodeTarget(\n nodeId: string,\n): { kind: 'point'; id: string } | { kind: 'object'; id: string } | null {\n if (nodeId.startsWith(POINT_NODE_PREFIX)) {\n return { kind: 'point', id: nodeId.slice(POINT_NODE_PREFIX.length) }\n }\n if (nodeId.startsWith(OBJECT_NODE_PREFIX)) {\n return { kind: 'object', id: nodeId.slice(OBJECT_NODE_PREFIX.length) }\n }\n return null\n}\n","import { useEffect, useLayoutEffect, useRef, type RefObject } from 'react'\nimport type { Stage } from 'konva/lib/Stage'\nimport type { CanvasSize } from '../coordinates/transform'\nimport type { PointId } from '../geometry/point'\nimport type { ObjectTypeName } from '../schemas/objectTypeName.schema'\nimport type { Store } from '../store/createStore'\nimport {\n advanceContact,\n beginContact,\n isWithinCanvas,\n nodeTarget,\n opensContact,\n toStagePoint,\n type GestureCancelReason,\n type PointerContact,\n type ScreenPoint,\n} from './gesturePolicy'\nimport type { ConnectionPreview } from './useConnectionPreview'\n\ntype EpochStore = Pick<Store<{ interactionEpoch?: number }>, 'getState' | 'subscribe'>\n\ntype PressTarget =\n | { kind: 'point'; id: PointId }\n | { kind: 'object'; id: string }\n | { kind: 'stage' }\n | { kind: 'foreign' }\n\ninterface ActiveContact extends PointerContact {\n // The store the contact went down on, its epoch then, and the handlers of\n // that render. A contact outlives nothing that replaced it (RFC 0004 §\n // Decision 4): not an undo, a load or a reset inside the store, and not the\n // store itself, which a host may swap for a fresh one to resume. Cleanup\n // after a cancellation goes through these handlers, so it lands on the store\n // the gesture was made on and never on the one that replaced it.\n store: EpochStore\n epoch: number\n handlers: PointerGestureHandlers\n // Where the press landed in the stage's own pixels. `origin` is the same\n // press in viewport pixels, which is what the threshold measures.\n stageOrigin: ScreenPoint\n target: Exclude<PressTarget, { kind: 'foreign' }>\n // What the press became: a connection whenever a segment or line tool is\n // armed, otherwise whatever it landed on.\n role: 'connect' | 'point' | 'object' | 'stage'\n // Where the pressed point was drawn when the contact began, for a point that\n // may be dragged; null for everything else.\n dragFrom: ScreenPoint | null\n captured: boolean\n}\n\nexport interface PointerGestureHandlers {\n tool: ObjectTypeName | null\n connection: ConnectionPreview\n placement: { trackAt(pointer: ScreenPoint): void; clearPointer(): void }\n // Where a drawn point is on screen, or null when this id is not one of the\n // figure's points (a goal preview's mark, for instance).\n pointOnScreen(id: PointId): ScreenPoint | null\n canDragPoint(id: PointId): boolean\n // The point's new screen position: the pointer's displacement since the\n // press, applied to where the point was. The canvas converts to math.\n dragPoint(id: PointId, to: ScreenPoint): void\n endPointDrag(id: PointId): void\n tapPoint(id: PointId): void\n tapObject(id: string): void\n tapStage(pointer: ScreenPoint): void\n // Forget a click-click first endpoint. Called on every cancellation.\n abandonConstruction(): void\n // Escape's extra duty: put the armed tool down.\n disarmTool(): void\n}\n\n/**\n * The one pointer lifecycle behind both canvases (RFC 0004 § Decision 4,\n * ADR 0011).\n *\n * Native Pointer Events on the stage's container, one contact at a time,\n * identified by `pointerId` and captured while it is down. A contact is a tap\n * until it moves its pointer type's threshold and a drag from then on, and it\n * becomes geometry only through a normal release on the canvas — no Konva\n * click, tap or drag handler, and no compatibility mouse event, can build or\n * select anything on its own, so one contact is at most one creation.\n *\n * Every other ending is a cancellation, and every cancellation clears the\n * preview, the snap marker, the pending endpoint and the contact, and closes a\n * point drag's undo step where it stands: a dragged point keeps the last\n * position it was moved to, and one Undo takes it back.\n */\nexport function usePointerGestures(\n stageRef: RefObject<Stage | null>,\n canvas: CanvasSize,\n store: EpochStore,\n handlers: PointerGestureHandlers,\n): void {\n // Listeners are installed once and read everything through refs: a release\n // may arrive in the same task as its press, before React has rendered, and\n // it must see the contact that press opened and the handlers of the latest\n // render rather than those of the render the listener was installed in.\n const latest = useRef({ handlers, canvas, store })\n useLayoutEffect(() => {\n latest.current = { handlers, canvas, store }\n })\n const contact = useRef<ActiveContact | null>(null)\n const cancelRef = useRef<(reason: GestureCancelReason) => void>(() => {})\n const storeInUse = useRef(store)\n\n useEffect(() => {\n const stage = stageRef.current\n if (!stage) return\n const container = stage.container()\n\n // Measured against Konva's content element, which is laid out at exactly\n // the stage's size: the container around it is the host's and may be\n // wider.\n function stagePoint(event: PointerEvent): ScreenPoint {\n const content = stage!.content\n return toStagePoint({ x: event.clientX, y: event.clientY }, content.getBoundingClientRect(), {\n width: content.clientWidth,\n height: content.clientHeight,\n })\n }\n\n function viewportPoint(event: PointerEvent): ScreenPoint {\n return { x: event.clientX, y: event.clientY }\n }\n\n // Every pointer currently pressed anywhere on the page. A contact opens\n // only when its pointer is the only one down, so a second input — a second\n // finger, or a finger beside a pen, each of which is its own type's primary\n // pointer — cancels the gesture in progress and does not start one of its\n // own until everything has lifted.\n const pressed = new Map<number, string>()\n\n // Konva's hit canvas, not the engine's hit test: the drawn targets are the\n // 22 px point and 44 px stroke regions M3.H.3 sized, and a node that does\n // not listen is not hit.\n function targetAt(pointer: ScreenPoint): PressTarget {\n const node = stage!.getIntersection(pointer)\n if (!node) return { kind: 'stage' }\n const target = nodeTarget(node.id())\n // A point mark that is not one of the figure's points — a goal\n // preview's — is drawn, not pressed.\n if (target?.kind === 'point' && !latest.current.handlers.pointOnScreen(target.id)) {\n return { kind: 'foreign' }\n }\n return target ?? { kind: 'foreign' }\n }\n\n function pointUnder(pointer: ScreenPoint): PointId | null {\n const target = targetAt(pointer)\n return target.kind === 'point' ? target.id : null\n }\n\n // Ends the contact's ownership: the pointer is released and nothing it\n // sends from now on is read. A capture released here is not a\n // cancellation — the contact is already gone when `lostpointercapture`\n // arrives, so the handler below finds nothing to cancel.\n function release(ended: ActiveContact): void {\n if (contact.current === ended) contact.current = null\n if (!ended.captured) return\n try {\n if (container.hasPointerCapture(ended.pointerId)) {\n container.releasePointerCapture(ended.pointerId)\n }\n } catch {\n // The pointer is already gone; there is nothing left to release.\n }\n }\n\n function cancel(reason: GestureCancelReason): void {\n const ended = contact.current\n if (ended) release(ended)\n const { handlers } = latest.current\n handlers.connection.cancel()\n handlers.placement.clearPointer()\n if (ended) {\n if (ended.role === 'point' && ended.dragging && ended.dragFrom) {\n ended.handlers.endPointDrag((ended.target as { id: PointId }).id)\n }\n ended.handlers.abandonConstruction()\n } else if (reason !== 'store-replaced') {\n // A store the canvas has only just been given has no gesture of this\n // canvas's to abandon, and a pending endpoint in it is not ours.\n handlers.abandonConstruction()\n }\n if (reason === 'escape' && handlers.tool) handlers.disarmTool()\n }\n cancelRef.current = cancel\n\n // A contact is stale once what it was aimed at has been replaced: by an\n // undo, a load or a tool change inside its store, or by the host swapping\n // the store out from under the canvas.\n function isStale(active: ActiveContact): boolean {\n if (latest.current.store !== active.store) return true\n return (active.store.getState().interactionEpoch ?? 0) !== active.epoch\n }\n\n function ownContact(event: PointerEvent): ActiveContact | null {\n const active = contact.current\n if (!active || active.pointerId !== event.pointerId) return null\n if (isStale(active)) {\n cancel('invalidated')\n return null\n }\n return active\n }\n\n function handlePointerDown(event: PointerEvent): void {\n if (contact.current || !opensContact(event)) return\n if (pressed.size > 1) return\n const pointer = stagePoint(event)\n const target = targetAt(pointer)\n if (target.kind === 'foreign') return\n const { handlers, store } = latest.current\n const connecting = handlers.tool === 'segment' || handlers.tool === 'line'\n const role = connecting ? 'connect' : target.kind\n const dragFrom =\n role === 'point' && target.kind === 'point' && handlers.canDragPoint(target.id)\n ? handlers.pointOnScreen(target.id)\n : null\n let captured = false\n try {\n container.setPointerCapture(event.pointerId)\n captured = true\n } catch {\n // A synthetic or already-lifted pointer cannot be captured. The\n // window listeners below still follow it, and the canvas-bounds check\n // on release still applies.\n }\n contact.current = {\n ...beginContact(event.pointerId, event.pointerType, viewportPoint(event)),\n store,\n epoch: store.getState().interactionEpoch ?? 0,\n handlers,\n stageOrigin: pointer,\n target,\n role,\n dragFrom,\n captured,\n }\n if (role === 'connect') {\n handlers.connection.press(pointer, target.kind === 'point' ? target.id : null)\n } else if (handlers.tool === 'point' && role === 'stage') {\n // A finger has no hover, so the marker is shown while it is down: the\n // student sees what the tap will land on before lifting.\n handlers.placement.trackAt(pointer)\n }\n }\n\n // Any second press anywhere while a contact is down — a second finger, a\n // pen beside a finger — cancels it. Registered on the window's capture\n // phase so it runs before the canvas's own press handler, which then\n // declines the new pointer because another one is still down.\n function handleAnyPointerDown(event: PointerEvent): void {\n // A primary pointer means no other pointer of its type is down, so an\n // entry of that type still listed lost its release somewhere — off the\n // window, or under a context menu — and would otherwise block the canvas\n // for good.\n if (event.isPrimary) {\n for (const [id, type] of pressed) {\n if (type === event.pointerType && id !== event.pointerId) pressed.delete(id)\n }\n }\n pressed.set(event.pointerId, event.pointerType)\n const active = contact.current\n if (active && active.pointerId !== event.pointerId) cancel('second-pointer')\n }\n\n function handleAnyPointerEnd(event: PointerEvent): void {\n pressed.delete(event.pointerId)\n }\n\n function handlePointerMove(event: PointerEvent): void {\n // A pointer moving with no button held is not pressed, whatever the\n // list says: the same self-repair as above, for a mouse or a pen.\n if (event.buttons === 0) pressed.delete(event.pointerId)\n const active = ownContact(event)\n if (!active) return\n const pointer = stagePoint(event)\n const advanced: ActiveContact = {\n ...active,\n ...advanceContact(active, viewportPoint(event)),\n }\n contact.current = advanced\n // Below the threshold nothing moves: no preview, no drag, no marker.\n if (!advanced.dragging) return\n const { handlers } = latest.current\n if (advanced.role === 'connect') {\n handlers.connection.drag(pointer)\n } else if (advanced.role === 'point' && advanced.dragFrom) {\n handlers.dragPoint((advanced.target as { id: PointId }).id, {\n x: advanced.dragFrom.x + pointer.x - advanced.stageOrigin.x,\n y: advanced.dragFrom.y + pointer.y - advanced.stageOrigin.y,\n })\n } else if (advanced.role === 'stage' && !active.dragging) {\n // A drag on bare canvas places nothing, so it stops showing where a\n // placement would land.\n handlers.placement.clearPointer()\n }\n }\n\n function handlePointerUp(event: PointerEvent): void {\n const active = ownContact(event)\n if (!active) return\n const pointer = stagePoint(event)\n if (!isWithinCanvas(pointer, latest.current.canvas)) {\n cancel('outside-release')\n return\n }\n const { dragging } = advanceContact(active, viewportPoint(event))\n release(active)\n const { handlers } = latest.current\n const { target } = active\n switch (active.role) {\n case 'connect': {\n const used = handlers.connection.release(pointer, pointUnder(pointer), dragging)\n if (!used && !dragging && target.kind === 'stage') handlers.tapStage(pointer)\n break\n }\n case 'point':\n if (!dragging) handlers.tapPoint((target as { id: PointId }).id)\n else if (active.dragFrom) handlers.endPointDrag((target as { id: PointId }).id)\n break\n case 'object':\n if (!dragging) handlers.tapObject((target as { id: string }).id)\n break\n case 'stage':\n if (!dragging) handlers.tapStage(pointer)\n break\n }\n // A mouse keeps hovering where it clicked; a lifted finger is nowhere.\n if (event.pointerType !== 'mouse' && event.pointerType !== 'pen') {\n handlers.placement.clearPointer()\n }\n }\n\n function handlePointerCancel(event: PointerEvent): void {\n if (ownContact(event)) cancel('pointercancel')\n }\n\n // Capture lost while the contact is still ours — the element was removed,\n // or the browser took the pointer. After a normal release the contact is\n // already gone, and this finds nothing.\n function handleLostCapture(event: PointerEvent): void {\n const active = contact.current\n if (active?.captured && active.pointerId === event.pointerId) cancel('lost-capture')\n }\n\n function handleHover(event: PointerEvent): void {\n if (contact.current || event.pointerType === 'touch') return\n const pointer = stagePoint(event)\n const { handlers } = latest.current\n handlers.connection.hover(pointer)\n if (handlers.tool === 'point') handlers.placement.trackAt(pointer)\n }\n\n function handleLeave(): void {\n if (contact.current) return\n const { handlers } = latest.current\n handlers.connection.hover(null)\n handlers.placement.clearPointer()\n }\n\n // A window without focus may never hear the releases of what was down,\n // so the pressed list starts again from nothing.\n function handleBlur(): void {\n pressed.clear()\n cancel('blur')\n }\n\n function handleVisibility(): void {\n if (document.visibilityState !== 'hidden') return\n pressed.clear()\n cancel('hidden')\n }\n\n // Escape disarms the tool. It is the only cancel a keyboard user has: the\n // stage is a canvas, so there is nothing on it to tab to and press again.\n function handleKeyDown(event: KeyboardEvent): void {\n if (event.key !== 'Escape') return\n if (!contact.current && !latest.current.handlers.tool) return\n cancel('escape')\n }\n\n const removers: Array<() => void> = []\n function listen<E extends Event>(\n target: EventTarget,\n type: string,\n handler: (event: E) => void,\n capture = false,\n ): void {\n const listener = handler as EventListener\n target.addEventListener(type, listener, capture)\n removers.push(() => target.removeEventListener(type, listener, capture))\n }\n listen(container, 'pointerdown', handlePointerDown)\n listen(container, 'pointermove', handleHover)\n listen(container, 'pointerleave', handleLeave)\n listen(container, 'lostpointercapture', handleLostCapture)\n listen(window, 'pointerdown', handleAnyPointerDown, true)\n listen(window, 'pointerup', handleAnyPointerEnd, true)\n listen(window, 'pointercancel', handleAnyPointerEnd, true)\n listen(window, 'pointermove', handlePointerMove)\n listen(window, 'pointerup', handlePointerUp)\n listen(window, 'pointercancel', handlePointerCancel)\n listen(window, 'blur', handleBlur)\n listen(window, 'keydown', handleKeyDown)\n listen(document, 'visibilitychange', handleVisibility)\n return () => {\n for (const remove of removers) remove()\n cancel('unmount')\n cancelRef.current = () => {}\n }\n }, [stageRef])\n\n // An undo, a redo, a load, a reset or a tool change moves the epoch. The\n // contact is dropped the moment it does, not at its next event, and a\n // preview hovering from a tapped endpoint goes with it — disarming and\n // re-arming a tool must not bring an old preview back.\n //\n // A new store is a new session — a host that resumes a saved attempt may\n // hand the canvas a fresh store — so a contact made on the old one is\n // dropped too, and its cleanup goes to the old store. Events that arrive\n // before this effect runs are already refused: `isStale()` compares stores.\n useEffect(() => {\n if (storeInUse.current !== store) {\n storeInUse.current = store\n cancelRef.current('store-replaced')\n }\n let seen = store.getState().interactionEpoch ?? 0\n return store.subscribe((next) => {\n const epoch = next.interactionEpoch ?? 0\n if (epoch === seen) return\n seen = epoch\n cancelRef.current('invalidated')\n })\n }, [store])\n\n // The placement a contact was pressed under no longer exists once the canvas\n // resizes: the same pixel is a different math coordinate.\n useEffect(() => {\n if (contact.current) cancelRef.current('resize')\n }, [canvas.width, canvas.height])\n}\n","import { Circle, Text } from 'react-konva'\nimport { mathToScreen, type CanvasSize } from '../../coordinates/transform'\nimport type { MathViewport } from '../../schemas/viewport.schema'\nimport type { SnapResult } from '../../snapping/computeSnap'\n\nconst snapColor = '#786af5'\n\n// Labels stay short because they sit next to the cursor; the host application\n// localizes user-facing copy elsewhere, this is a development affordance in\n// the same spirit as the point labels.\nconst snapLabels: Record<SnapResult['kind'], string> = {\n point: 'point',\n intersection: 'intersection',\n grid: 'grid',\n onObject: 'on object',\n}\n\n// Shows where a click would actually land while the point tool is armed.\n// Without it, snapping silently moves the student's point and reads as a bug.\nexport function SnapMarker({\n snap,\n viewport,\n canvas,\n}: {\n snap: SnapResult\n viewport: MathViewport\n canvas: CanvasSize\n}) {\n const screen = mathToScreen(snap.position, viewport, canvas)\n return (\n <>\n <Circle\n x={screen.x}\n y={screen.y}\n radius={12}\n stroke={snapColor}\n strokeWidth={2}\n dash={[4, 3]}\n listening={false}\n />\n <Circle x={screen.x} y={screen.y} radius={3.5} fill={snapColor} listening={false} />\n <Text\n x={screen.x + 15}\n y={screen.y + 8}\n text={snapLabels[snap.kind]}\n fontSize={13}\n fontStyle=\"bold\"\n fill={snapColor}\n listening={false}\n />\n </>\n )\n}\n","import { Circle, Text } from 'react-konva'\nimport { mathToScreen, type CanvasSize } from '../../coordinates/transform'\nimport type { Point, PointId } from '../../geometry/point'\nimport type { MathViewport } from '../../schemas/viewport.schema'\nimport { pointNodeId } from '../gesturePolicy'\n\nexport interface PointShapeProps {\n id: PointId\n position: Point\n viewport: MathViewport\n canvas: CanvasSize\n selected?: boolean\n connectable?: boolean\n snapTarget?: boolean\n label?: string\n color?: string\n feedbackRingColor?: string\n feedbackRingDashed?: boolean\n}\n\n// The activation radius every drawn point answers to, in CSS pixels\n// (RFC 0004 § Decision 3). Konva hit-tests a circle's fill plus half its hit\n// stroke, so the stroke is whatever closes the gap between the drawn radius —\n// which is unchanged, because a bigger target is not a bigger mark — and this.\n// It is under the 24 px placement snap radius on purpose: a click the shape\n// takes is one the snap would have taken anyway.\nconst POINT_HIT_RADIUS = 22\n\n// Draws a point and its activation region, and nothing else: it handles no\n// event. Pressing, tapping and dragging a point are the canvas's shared pointer\n// lifecycle (`usePointerGestures`, RFC 0004 § Decision 4), which finds the\n// point through this node's `pointNodeId()` on Konva's hit canvas — so\n// the region below is exactly what a finger or a mouse has to land in.\nexport function PointShape({\n id,\n position,\n viewport,\n canvas,\n selected = false,\n connectable = false,\n snapTarget = false,\n label,\n color = '#24304a',\n feedbackRingColor,\n feedbackRingDashed = false,\n}: PointShapeProps) {\n const screen = mathToScreen(position, viewport, canvas)\n const radius = snapTarget ? 13 : selected ? 13 : connectable ? 10 : 9\n\n return (\n <>\n {feedbackRingColor && (\n // Dashed for incorrect, solid for correct: the ring has to say which\n // verdict it carries without relying on its colour.\n <Circle\n x={screen.x}\n y={screen.y}\n radius={15}\n stroke={feedbackRingColor}\n strokeWidth={4}\n dash={feedbackRingDashed ? [7, 5] : undefined}\n listening={false}\n />\n )}\n {snapTarget && (\n <Circle\n x={screen.x}\n y={screen.y}\n radius={20}\n fill=\"#786af526\"\n stroke=\"#786af5\"\n strokeWidth={2}\n shadowColor=\"#786af5\"\n shadowBlur={18}\n listening={false}\n />\n )}\n <Circle\n id={pointNodeId(id)}\n x={screen.x}\n y={screen.y}\n radius={radius}\n fill={snapTarget || selected ? '#6d5dfc' : color}\n stroke={connectable && !selected && !snapTarget ? '#a69df8' : '#fff'}\n strokeWidth={3}\n hitStrokeWidth={2 * (POINT_HIT_RADIUS - radius)}\n shadowColor=\"#24304a\"\n shadowBlur={selected ? 12 : 4}\n />\n <Text\n x={screen.x + 13}\n y={screen.y - 23}\n text={label ?? id}\n fontSize={18}\n fontStyle=\"bold\"\n fill={color}\n listening={false}\n />\n </>\n )\n}\n","import { Line as KonvaLine } from 'react-konva'\nimport { objectNodeId } from '../gesturePolicy'\n\n// Shared Konva rendering for SegmentShape/LineShape: both draw a stroked\n// line between two already-resolved screen points, differing only in how\n// those two points are computed (direct projection vs viewport clipping).\n\n// The 44 CSS px activation stroke a selectable object answers to\n// (RFC 0004 § Decision 3). It is the hit stroke alone: the drawn stroke keeps\n// its authored width, because a segment a finger can reach is not a segment\n// drawn four times as thick. Points are drawn after objects and so win every\n// overlap, which is the priority the contract asks for.\nconst SELECTABLE_HIT_STROKE = 44\n\n// The shape handles no event itself. It carries an `objectNodeId()` so the\n// canvas's pointer lifecycle can find it on Konva's hit canvas, and a press, a\n// tap or a drag on it is classified there, once, for every kind\n// (RFC 0004 § Decision 4).\n\nexport interface StrokedLineProps {\n id: string\n a: { x: number; y: number }\n b: { x: number; y: number }\n color: string\n strokeWidth: number\n dashed: boolean\n selected: boolean\n opacity: number\n listening: boolean\n}\n\nexport function StrokedLine({\n id,\n a,\n b,\n color,\n strokeWidth,\n dashed,\n selected,\n opacity,\n listening,\n}: StrokedLineProps) {\n return (\n <KonvaLine\n id={objectNodeId(id)}\n points={[a.x, a.y, b.x, b.y]}\n stroke={color}\n strokeWidth={selected ? strokeWidth + 3 : strokeWidth}\n lineCap=\"round\"\n dash={dashed ? [10, 6] : undefined}\n hitStrokeWidth={SELECTABLE_HIT_STROKE}\n opacity={opacity}\n listening={listening}\n />\n )\n}\n","import { mathToScreen, type CanvasSize } from '../../coordinates/transform'\nimport type { Point } from '../../geometry/point'\nimport type { MathViewport } from '../../schemas/viewport.schema'\nimport { StrokedLine } from './StrokedLine'\n\nexport interface SegmentShapeProps {\n id: string\n from: Point\n to: Point\n viewport: MathViewport\n canvas: CanvasSize\n color?: string\n strokeWidth?: number\n dashed?: boolean\n selected?: boolean\n opacity?: number\n listening?: boolean\n}\n\nexport function SegmentShape({\n id,\n from,\n to,\n viewport,\n canvas,\n color = '#6d5dfc',\n strokeWidth = 5,\n dashed = false,\n selected = false,\n opacity = 1,\n listening = true,\n}: SegmentShapeProps) {\n const a = mathToScreen(from, viewport, canvas)\n const b = mathToScreen(to, viewport, canvas)\n\n return (\n <StrokedLine\n id={id}\n a={a}\n b={b}\n color={color}\n strokeWidth={strokeWidth}\n dashed={dashed}\n selected={selected}\n opacity={opacity}\n listening={listening}\n />\n )\n}\n","import { mathToScreen, type CanvasSize } from '../../coordinates/transform'\nimport { clipLineToViewport } from '../../coordinates/lineClipping'\nimport type { Point } from '../../geometry/point'\nimport type { MathViewport } from '../../schemas/viewport.schema'\nimport { StrokedLine } from './StrokedLine'\n\nexport interface LineShapeProps {\n id: string\n through: [Point, Point]\n viewport: MathViewport\n canvas: CanvasSize\n color?: string\n strokeWidth?: number\n dashed?: boolean\n selected?: boolean\n opacity?: number\n listening?: boolean\n}\n\n// Clips the infinite line to the viewport edges (math space) then draws a\n// bounded Konva Line between the two boundary points — visually\n// indistinguishable from an infinite line within the visible viewport.\nexport function LineShape({\n id,\n through,\n viewport,\n canvas,\n color = '#6d5dfc',\n strokeWidth = 4,\n dashed = false,\n selected = false,\n opacity = 1,\n listening = true,\n}: LineShapeProps) {\n const clipped = clipLineToViewport(through[0], through[1], viewport)\n if (!clipped) return null\n\n const a = mathToScreen(clipped[0], viewport, canvas)\n const b = mathToScreen(clipped[1], viewport, canvas)\n\n return (\n <StrokedLine\n id={id}\n a={a}\n b={b}\n color={color}\n strokeWidth={strokeWidth}\n dashed={dashed}\n selected={selected}\n opacity={opacity}\n listening={listening}\n />\n )\n}\n","import { Ellipse } from 'react-konva'\nimport { computePlacement, mathToScreen, type CanvasSize } from '../../coordinates/transform'\nimport type { Point } from '../../geometry/point'\nimport type { MathViewport } from '../../schemas/viewport.schema'\nimport { objectNodeId } from '../gesturePolicy'\n\n// As in StrokedLine, and for the same reason: the hit stroke is the target,\n// the drawn stroke is the mark (RFC 0004 § Decision 3).\nconst SELECTABLE_HIT_STROKE = 44\n\nexport interface CircleShapeProps {\n id: string\n center: Point\n radius: number\n viewport: MathViewport\n canvas: CanvasSize\n color?: string\n strokeWidth?: number\n dashed?: boolean\n selected?: boolean\n opacity?: number\n listening?: boolean\n}\n\n// A circle drawn as a Konva ellipse, not a Konva circle: under the `stretch`\n// projection the two axes carry different scales, and a mathematical circle is\n// then an ellipse on screen. Under the default `contain` projection the two\n// radii come out equal and it is a circle again — so the ellipse is the shape\n// that is right under both, rather than the one that is right under one and\n// silently wrong under the other.\n//\n// Stroked and never filled: a circle is a locus, and filling it would make the\n// whole disc claim the clicks that belong to whatever is drawn inside it —\n// which is the same reason `distanceTo` measures to the curve.\nexport function CircleShape({\n id,\n center,\n radius,\n viewport,\n canvas,\n color = '#6d5dfc',\n strokeWidth = 4,\n dashed = false,\n selected = false,\n opacity = 1,\n listening = true,\n}: CircleShapeProps) {\n const placement = computePlacement(viewport, canvas)\n const screenCenter = mathToScreen(center, viewport, canvas, placement)\n\n return (\n <Ellipse\n id={objectNodeId(id)}\n x={screenCenter.x}\n y={screenCenter.y}\n radiusX={radius * placement.scaleX}\n radiusY={radius * placement.scaleY}\n stroke={color}\n strokeWidth={selected ? strokeWidth + 3 : strokeWidth}\n dash={dashed ? [10, 6] : undefined}\n hitStrokeWidth={SELECTABLE_HIT_STROKE}\n opacity={opacity}\n listening={listening}\n />\n )\n}\n","import { Text } from 'react-konva'\nimport { mathToScreen, type CanvasSize } from '../../coordinates/transform'\nimport { clipLineToViewport } from '../../coordinates/lineClipping'\nimport type { ShapeDescriptor } from '../../geometry/entityModel'\nimport type { Point } from '../../geometry/point'\nimport type { MathViewport } from '../../schemas/viewport.schema'\n\n// An authored `label` drawn next to an object (M2.G.4.b).\n//\n// A point carries its own label inside `PointShape`, which has an obvious\n// anchor — the point. A segment, a line and a circle have none, so choosing one\n// is this file's whole subject, and it is a drawing decision rather than a\n// capability one: the record that supplies the text is the same\n// `presentation` entry `capabilitiesOf` reads.\n//\n// The anchor is computed in **math space** and projected once, so a label\n// follows its object through a pan, a zoom and the `stretch` projection the\n// same way the object does.\nexport interface EntityLabelProps {\n text: string\n descriptor: ShapeDescriptor\n viewport: MathViewport\n canvas: CanvasSize\n color?: string\n opacity?: number\n}\n\ntype LabelAnchors = {\n readonly [S in ShapeDescriptor['shape']]: (\n descriptor: Extract<ShapeDescriptor, { shape: S }>,\n viewport: MathViewport,\n ) => Point | null\n}\n\n// A mapped type over the descriptor union, exhaustive the same way\n// `shapeRenderers` is: a new descriptor is a compile error here until it says\n// where its label goes.\nconst labelAnchors: LabelAnchors = {\n // `EntityShape` never draws a point — `PointShape` does, label included — so\n // a point descriptor reaching here anchors nothing rather than growing a\n // second label next to the one the canvas already drew.\n point: () => null,\n // The midpoint: the one place on a segment that is on the object, is\n // independent of which endpoint was authored first, and does not collide\n // with either endpoint's own point label.\n segment: (descriptor) => midpoint(descriptor.from, descriptor.to),\n // The midpoint of the **clipped** span, not of the two authored points: a\n // line is drawn across the whole viewport and its defining points are often\n // outside it, so anchoring to them would leave the label off screen while the\n // line is plainly visible. Clipped away entirely, the line draws nothing and\n // neither does its label.\n line: (descriptor, viewport) => {\n const clipped = clipLineToViewport(descriptor.through[0], descriptor.through[1], viewport)\n return clipped ? midpoint(clipped[0], clipped[1]) : null\n },\n // The top of the circle. The centre is not on the locus and is usually where\n // the centre point's own label already sits; the top is on the curve, is\n // independent of any parametrization, and is the position a labelled circle\n // is conventionally annotated at.\n circle: (descriptor) => ({ x: descriptor.center.x, y: descriptor.center.y + descriptor.radius }),\n}\n\nexport function labelAnchorOf(descriptor: ShapeDescriptor, viewport: MathViewport): Point | null {\n // The same erasure the shape table makes: the key comes off the descriptor's\n // own `shape` field, so the entry selected is the one whose signature it\n // satisfies.\n const anchorOf = labelAnchors[descriptor.shape] as (\n descriptor: ShapeDescriptor,\n viewport: MathViewport,\n ) => Point | null\n return anchorOf(descriptor, viewport)\n}\n\nexport function EntityLabel({\n text,\n descriptor,\n viewport,\n canvas,\n color = '#24304a',\n opacity = 1,\n}: EntityLabelProps) {\n const anchor = labelAnchorOf(descriptor, viewport)\n if (!anchor) return null\n const screen = mathToScreen(anchor, viewport, canvas)\n\n return (\n // Offset up and to the right of the anchor, matching `PointShape`'s label,\n // so an object label and a point label read as the same annotation. Never\n // listening: the label is not the object, and a click on it must reach\n // whatever is drawn underneath.\n <Text\n x={screen.x + 10}\n y={screen.y - 24}\n text={text}\n fontSize={18}\n fontStyle=\"bold\"\n fill={color}\n opacity={opacity}\n listening={false}\n />\n )\n}\n\nfunction midpoint(a: Point, b: Point): Point {\n return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 }\n}\n","import type { ReactElement } from 'react'\nimport type { CanvasSize } from '../../coordinates/transform'\nimport type { ShapeDescriptor } from '../../geometry/entityModel'\nimport type { MathViewport } from '../../schemas/viewport.schema'\nimport { CircleShape } from './CircleShape'\nimport { EntityLabel } from './EntityLabel'\nimport { LineShape } from './LineShape'\nimport { SegmentShape } from './SegmentShape'\n\n// One shape descriptor, drawn (M2.8).\n//\n// Before this file each canvas branched on a stored object's `type` to choose\n// between `SegmentShape` and `LineShape` — a ternary in the student canvas, two\n// separate arrays in the teacher one — so a third kind meant editing both. Here\n// the canvas asks the registry what a resolved entity looks like\n// (`shapeOfResolved`) and hands the answer over; which component draws it is\n// this table's business.\n//\n// The table is a mapped type over the descriptor union, so it is exhaustive the\n// same way `EntityRegistry` and the relation tables are: a descriptor added to\n// `ShapeDescriptor` is a compile error here until it can be drawn.\nexport interface EntityShapeProps {\n id: string\n descriptor: ShapeDescriptor\n viewport: MathViewport\n canvas: CanvasSize\n color?: string\n strokeWidth?: number\n dashed?: boolean\n selected?: boolean\n opacity?: number\n listening?: boolean\n // An authored `presentation` label. Drawn beside the object at an anchor\n // `EntityLabel` chooses per kind (M2.G.4.b); absent, the object carries no\n // text — an object has no id fallback the way a point does, because a\n // segment's id is a construction name rather than a name for the figure.\n label?: string\n}\n\ntype Styling = Omit<EntityShapeProps, 'descriptor'>\n\ntype ShapeRenderers = {\n readonly [S in ShapeDescriptor['shape']]: (\n descriptor: Extract<ShapeDescriptor, { shape: S }>,\n styling: Styling,\n ) => ReactElement | null\n}\n\nconst shapeRenderers: ShapeRenderers = {\n // A point is drawn by `PointShape`, which carries the affordances only a\n // point has — dragging, a label, a feedback ring — and which the canvases\n // therefore render from their own point map rather than from a locus list. A\n // descriptor arriving here is a locus the canvas is drawing; answering null\n // for a point says so rather than drawing a second, styling-less copy of one.\n point: () => null,\n segment: (descriptor, styling) => (\n <SegmentShape {...styling} from={descriptor.from} to={descriptor.to} />\n ),\n line: (descriptor, styling) => (\n <LineShape {...styling} through={[descriptor.through[0], descriptor.through[1]]} />\n ),\n circle: (descriptor, styling) => (\n <CircleShape {...styling} center={descriptor.center} radius={descriptor.radius} />\n ),\n}\n\nexport function EntityShape({\n descriptor,\n label,\n ...styling\n}: EntityShapeProps): ReactElement | null {\n // The same erasure the entity registry makes at its own dispatchers: the key\n // is read off the descriptor's own `shape` field, so the entry selected is by\n // construction the one whose signature it satisfies.\n const render = shapeRenderers[descriptor.shape] as (\n descriptor: ShapeDescriptor,\n styling: Styling,\n ) => ReactElement | null\n const drawn = render(descriptor, styling)\n // A label annotates a drawn object, so a point — whose label `PointShape`\n // owns, and which this table answers null for — is not annotated here. A line\n // clipped out of the viewport is refused one edge further in: it decides\n // whether it is on screen while rendering, and `EntityLabel` draws nothing at\n // an anchor the same clipping rejects.\n if (!drawn || label === undefined) return drawn\n return (\n <>\n {drawn}\n <EntityLabel\n text={label}\n descriptor={descriptor}\n viewport={styling.viewport}\n canvas={styling.canvas}\n color={styling.color}\n opacity={styling.opacity}\n />\n </>\n )\n}\n","import { Line, Rect } from 'react-konva'\nimport { computePlacement, mathToScreen, type CanvasSize } from '../coordinates/transform'\nimport { gridSteps } from '../coordinates/grid'\nimport type { MathViewport } from '../schemas/viewport.schema'\n\nexport function GeometryGrid({\n viewport,\n canvas,\n visible,\n}: {\n viewport: MathViewport\n canvas: CanvasSize\n visible: boolean\n}) {\n const [xMin, yMin, xMax, yMax] = viewport\n const placement = computePlacement(viewport, canvas)\n const topLeft = mathToScreen({ x: xMin, y: yMax }, viewport, canvas, placement)\n const bottomRight = mathToScreen({ x: xMax, y: yMin }, viewport, canvas, placement)\n const { x: verticalStep, y: horizontalStep } = gridSteps(viewport)\n const verticals = []\n const horizontals = []\n\n if (visible) {\n for (let x = Math.ceil(xMin / verticalStep) * verticalStep; x <= xMax; x += verticalStep) {\n const screen = mathToScreen({ x, y: 0 }, viewport, canvas, placement)\n verticals.push(\n <Line\n key={`grid-x-${x}`}\n points={[screen.x, topLeft.y, screen.x, bottomRight.y]}\n stroke=\"#e6e9f2\"\n strokeWidth={1}\n listening={false}\n />,\n )\n }\n for (\n let y = Math.ceil(yMin / horizontalStep) * horizontalStep;\n y <= yMax;\n y += horizontalStep\n ) {\n const screen = mathToScreen({ x: 0, y }, viewport, canvas, placement)\n horizontals.push(\n <Line\n key={`grid-y-${y}`}\n points={[topLeft.x, screen.y, bottomRight.x, screen.y]}\n stroke=\"#e6e9f2\"\n strokeWidth={1}\n listening={false}\n />,\n )\n }\n }\n\n return (\n <>\n {verticals}\n {horizontals}\n <Rect\n x={topLeft.x + 0.5}\n y={topLeft.y + 0.5}\n width={Math.max(0, bottomRight.x - topLeft.x - 1)}\n height={Math.max(0, bottomRight.y - topLeft.y - 1)}\n stroke=\"#cfd5e3\"\n strokeWidth={1}\n listening={false}\n />\n </>\n )\n}\n","import { useState } from 'react'\nimport { Arc, Circle, Group, Line, Rect, Text } from 'react-konva'\nimport type { KonvaEventObject } from 'konva/lib/Node'\nimport type { ToolName } from '../schemas/exercise.schema'\nimport type { CanvasSize } from '../coordinates/transform'\n\ninterface InstrumentTransform {\n x: number\n y: number\n rotation: number\n flipped: boolean\n}\n\nexport interface GeometryInstrumentsProps {\n tools: ToolName[]\n canvas: CanvasSize\n}\n\nconst instrumentColor = '#5b6b82'\nconst instrumentFill = 'rgba(211, 225, 242, 0.58)'\nconst controlColor = '#6558e8'\n\nfunction initialTransform(tool: ToolName, canvas: CanvasSize): InstrumentTransform {\n if (tool === 'ruler')\n return { x: canvas.width * 0.5, y: canvas.height * 0.72, rotation: 0, flipped: false }\n if (tool === 'setSquare')\n return { x: canvas.width * 0.32, y: canvas.height * 0.46, rotation: 0, flipped: false }\n return { x: canvas.width * 0.68, y: canvas.height * 0.46, rotation: 0, flipped: false }\n}\n\nfunction RulerBody() {\n const ticks = Array.from({ length: 21 }, (_, index) => index)\n return (\n <Group listening={false}>\n <Rect\n x={-150}\n y={-30}\n width={300}\n height={60}\n cornerRadius={5}\n fill={instrumentFill}\n stroke={instrumentColor}\n strokeWidth={2}\n />\n {ticks.map((tick) => {\n const x = -140 + tick * 14\n const major = tick % 2 === 0\n return (\n <Line\n key={tick}\n points={[x, -29, x, -29 + (major ? 17 : 10)]}\n stroke={instrumentColor}\n strokeWidth={major ? 1.5 : 1}\n />\n )\n })}\n {ticks\n .filter((tick) => tick % 2 === 0)\n .map((tick) => (\n <Text\n key={`label-${tick}`}\n x={-145 + tick * 14}\n y={-8}\n width={20}\n align=\"center\"\n text={String(tick / 2)}\n fontSize={10}\n fill={instrumentColor}\n />\n ))}\n </Group>\n )\n}\n\nfunction SetSquareBody() {\n return (\n <Group listening={false}>\n <Line\n points={[-105, 70, 105, 70, -105, -75]}\n closed\n fill={instrumentFill}\n stroke={instrumentColor}\n strokeWidth={2}\n lineJoin=\"round\"\n />\n <Line\n points={[-72, 48, 48, 48, -72, -34]}\n closed\n fill=\"#fbfcff\"\n stroke={instrumentColor}\n strokeWidth={1.5}\n lineJoin=\"round\"\n />\n {Array.from({ length: 11 }, (_, index) => (\n <Line\n key={index}\n points={[-91 + index * 18, 70, -91 + index * 18, index % 5 === 0 ? 54 : 60]}\n stroke={instrumentColor}\n strokeWidth={1}\n />\n ))}\n </Group>\n )\n}\n\nfunction ProtractorBody() {\n const ticks = Array.from({ length: 37 }, (_, index) => index * 5)\n return (\n <Group listening={false}>\n <Arc\n x={0}\n y={0}\n innerRadius={38}\n outerRadius={112}\n angle={180}\n rotation={180}\n fill={instrumentFill}\n stroke={instrumentColor}\n strokeWidth={2}\n />\n <Line points={[-112, 0, 112, 0]} stroke={instrumentColor} strokeWidth={2} />\n {ticks.map((degrees) => {\n const radians = (Math.PI * degrees) / 180\n const major = degrees % 10 === 0\n const outer = 108\n const inner = major ? 94 : 100\n return (\n <Line\n key={degrees}\n points={[\n Math.cos(Math.PI - radians) * outer,\n -Math.sin(radians) * outer,\n Math.cos(Math.PI - radians) * inner,\n -Math.sin(radians) * inner,\n ]}\n stroke={instrumentColor}\n strokeWidth={major ? 1.4 : 0.8}\n />\n )\n })}\n <Circle x={0} y={0} radius={3} fill={instrumentColor} />\n </Group>\n )\n}\n\nfunction InstrumentBody({ tool }: { tool: ToolName }) {\n if (tool === 'ruler') return <RulerBody />\n if (tool === 'setSquare') return <SetSquareBody />\n return <ProtractorBody />\n}\n\nfunction rotatePoint(x: number, y: number, degrees: number) {\n const radians = (degrees * Math.PI) / 180\n return {\n x: x * Math.cos(radians) - y * Math.sin(radians),\n y: x * Math.sin(radians) + y * Math.cos(radians),\n }\n}\n\nfunction gripOffset(tool: ToolName, flipped: boolean) {\n const offset =\n tool === 'ruler' ? { x: 0, y: 19 } : tool === 'setSquare' ? { x: -42, y: 24 } : { x: 0, y: -20 }\n return { x: flipped ? -offset.x : offset.x, y: offset.y }\n}\n\nexport function GeometryInstruments({ tools, canvas }: GeometryInstrumentsProps) {\n const [transforms, setTransforms] = useState<Partial<Record<ToolName, InstrumentTransform>>>(() =>\n Object.fromEntries(tools.map((tool) => [tool, initialTransform(tool, canvas)])),\n )\n const [selected, setSelected] = useState<ToolName | null>(null)\n const activeTool = selected && tools.includes(selected) ? selected : null\n\n function update(tool: ToolName, change: Partial<InstrumentTransform>) {\n setTransforms((current) => ({\n ...current,\n [tool]: { ...(current[tool] ?? initialTransform(tool, canvas)), ...change },\n }))\n }\n\n return (\n <>\n {tools.map((tool) => {\n const transform = transforms[tool] ?? initialTransform(tool, canvas)\n const active = activeTool === tool\n const localGrip = gripOffset(tool, transform.flipped)\n const rotatedGrip = rotatePoint(localGrip.x, localGrip.y, transform.rotation)\n const grip = { x: transform.x + rotatedGrip.x, y: transform.y + rotatedGrip.y }\n const rotationOffset = rotatePoint(0, -105, transform.rotation)\n const flipDistance = tool === 'setSquare' ? 96 : tool === 'ruler' ? 50 : 48\n const flipOffset = rotatePoint(0, flipDistance, transform.rotation)\n return (\n <Group key={tool}>\n <Group\n x={transform.x}\n y={transform.y}\n rotation={transform.rotation}\n scaleX={transform.flipped ? -1 : 1}\n listening={false}\n >\n <InstrumentBody tool={tool} />\n </Group>\n\n {active && (\n <>\n <Line\n points={[\n transform.x,\n transform.y,\n transform.x + rotationOffset.x,\n transform.y + rotationOffset.y,\n ]}\n stroke={controlColor}\n strokeWidth={1.5}\n dash={[4, 4]}\n listening={false}\n />\n <Circle\n x={transform.x + rotationOffset.x}\n y={transform.y + rotationOffset.y}\n radius={11}\n fill=\"#fff\"\n stroke={controlColor}\n strokeWidth={2.5}\n draggable\n onDragMove={(event: KonvaEventObject<DragEvent>) => {\n const dx = event.target.x() - transform.x\n const dy = event.target.y() - transform.y\n update(tool, { rotation: (Math.atan2(dy, dx) * 180) / Math.PI + 90 })\n }}\n onMouseDown={(event) => {\n event.cancelBubble = true\n }}\n onTouchStart={(event) => {\n event.cancelBubble = true\n }}\n />\n <Text\n x={transform.x + rotationOffset.x - 8}\n y={transform.y + rotationOffset.y - 8}\n width={16}\n height={16}\n text=\"↻\"\n align=\"center\"\n verticalAlign=\"middle\"\n fontSize={14}\n fontStyle=\"bold\"\n fill={controlColor}\n listening={false}\n />\n <Rect\n x={transform.x + flipOffset.x - 29}\n y={transform.y + flipOffset.y - 11}\n width={58}\n height={22}\n cornerRadius={11}\n fill=\"#fff\"\n stroke={controlColor}\n strokeWidth={1.5}\n onClick={(event) => {\n event.cancelBubble = true\n update(tool, { flipped: !transform.flipped })\n }}\n onTap={(event) => {\n event.cancelBubble = true\n update(tool, { flipped: !transform.flipped })\n }}\n />\n <Text\n x={transform.x + flipOffset.x - 27}\n y={transform.y + flipOffset.y - 7}\n width={54}\n height={14}\n text=\"⇄ Flip\"\n align=\"center\"\n verticalAlign=\"middle\"\n fontSize={10}\n fontStyle=\"600\"\n fill={controlColor}\n listening={false}\n />\n </>\n )}\n\n <Circle\n id={`geometry-instrument-${tool}-move-handle`}\n x={grip.x}\n y={grip.y}\n radius={active ? 8 : 12}\n fill={active ? 'rgba(255, 255, 255, 0.94)' : 'rgba(255, 255, 255, 0.01)'}\n stroke={controlColor}\n strokeWidth={active ? 1.5 : 0}\n shadowColor=\"#24304a\"\n shadowBlur={active ? 2 : 0}\n draggable\n dragBoundFunc={(position) => ({\n x: Math.max(22, Math.min(canvas.width - 22, position.x)),\n y: Math.max(22, Math.min(canvas.height - 22, position.y)),\n })}\n onDragStart={() => setSelected(tool)}\n onDragMove={(event: KonvaEventObject<DragEvent>) =>\n update(tool, {\n x: event.target.x() - rotatedGrip.x,\n y: event.target.y() - rotatedGrip.y,\n })\n }\n onMouseEnter={(event) => {\n const stage = event.target.getStage()\n if (stage) stage.container().style.cursor = 'grab'\n }}\n onMouseLeave={(event) => {\n const stage = event.target.getStage()\n if (stage) stage.container().style.cursor = 'default'\n }}\n onClick={(event) => {\n event.cancelBubble = true\n setSelected(tool)\n }}\n onTap={(event) => {\n event.cancelBubble = true\n setSelected(tool)\n }}\n />\n {active ? (\n <Text\n x={grip.x - 6}\n y={grip.y - 5}\n width={12}\n height={10}\n text=\"✣\"\n align=\"center\"\n verticalAlign=\"middle\"\n fontSize={9}\n fontStyle=\"bold\"\n fill={controlColor}\n listening={false}\n />\n ) : (\n <Circle\n x={grip.x}\n y={grip.y}\n radius={2.5}\n fill={instrumentColor}\n opacity={0.65}\n listening={false}\n />\n )}\n </Group>\n )\n })}\n </>\n )\n}\n","import { useSyncExternalStore } from 'react'\nimport type { Store } from './createStore'\n\n// The only file in store/ that imports React — kept separate from\n// createStore.ts so the store primitive itself stays usable from\n// non-React contexts (tests, a future non-React embed).\nexport function useStoreState<T>(store: Store<T>): T {\n return useSyncExternalStore(\n (listener) => store.subscribe(listener),\n () => store.getState(),\n )\n}\n","import { useEffect, useRef } from 'react'\nimport { Layer, Line as KonvaLine, Rect, Stage } from 'react-konva'\nimport type { Stage as KonvaStage } from 'konva/lib/Stage'\nimport { mathToScreen, screenToMath, type CanvasSize } from '../coordinates/transform'\nimport { deriveRenderableGeometry } from './deriveRenderableGeometry'\nimport { useConnectionPreview } from './useConnectionPreview'\nimport { usePlacementSnap } from './usePlacementSnap'\nimport { usePointerGestures } from './usePointerGestures'\nimport { GESTURE_SURFACE_STYLE, type ScreenPoint } from './gesturePolicy'\nimport { SnapMarker } from './shapes/SnapMarker'\nimport { PointShape } from './shapes/PointShape'\nimport { SegmentShape } from './shapes/SegmentShape'\nimport { LineShape } from './shapes/LineShape'\nimport { EntityShape } from './shapes/EntityShape'\nimport { resolveAgainstPoints, shapeOfResolved } from '../geometry/entities'\nimport { originForSnap } from '../snapping/computeSnap'\nimport { GeometryGrid } from './GeometryGrid'\nimport { GeometryInstruments } from './GeometryInstruments'\nimport {\n addGivenPoint,\n cancelGivenConstruction,\n endGivenPointDrag,\n executeGivenConstruction,\n isGivenPointDraggable,\n moveGivenPoint,\n pickPointForPendingObject,\n promoteGivenPoint,\n selectGiven,\n selectPendingObjectType,\n type TeacherEditorState,\n} from '../store/teacherStore'\nimport { useStoreState } from '../store/useStoreState'\nimport type { Store } from '../store/createStore'\nimport type { GeometryExercise } from '../schemas/exercise.schema'\nimport type { RevealedAnswer } from '../validation/types'\n\nexport interface GeometryCanvasProps {\n // Teacher editing canvas. Student interactions are exposed separately by\n // StudentGeometryCanvas so each mode keeps a focused, typed store contract.\n store: Store<TeacherEditorState>\n size: CanvasSize\n onExerciseChange?: (exercise: GeometryExercise) => void\n onGivenSelect?: (id: string) => void\n highlightedGivenIds?: string[]\n goalPreviews?: RevealedAnswer[]\n}\n\nexport function GeometryCanvas({\n store,\n size,\n onExerciseChange,\n onGivenSelect,\n highlightedGivenIds = [],\n goalPreviews = [],\n}: GeometryCanvasProps) {\n const state = useStoreState(store)\n const { points, objects, capabilitiesOf, labelOf, isSnapTarget } = deriveRenderableGeometry(state)\n const { viewport } = state.exercise\n const stageRef = useRef<KonvaStage>(null)\n const connection = useConnectionPreview({\n points,\n viewport,\n canvas: size,\n pendingObjectType: state.pendingObjectType,\n pendingFirstPointId: state.pendingFirstPointId,\n connect: (from, to) => {\n if (state.pendingObjectType !== 'segment' && state.pendingObjectType !== 'line') return\n executeGivenConstruction(store, { tool: state.pendingObjectType, from, to })\n },\n pick: (id) => {\n pickPointForPendingObject(store, id)\n onGivenSelect?.(id)\n },\n objects,\n isSnapTarget,\n canCreatePoints: true,\n preserveCreatedPointOrigins: false,\n })\n const { connecting, previewPoints: connectionPreviewPoints, snapTargetId } = connection\n const placementSnap = usePlacementSnap({\n points,\n objects,\n viewport,\n canvas: size,\n showGrid: Boolean(state.exercise.showGrid),\n isSnapTarget,\n active: state.pendingObjectType === 'point',\n })\n\n useEffect(() => {\n if (!onExerciseChange) return\n return store.subscribe((next) => onExerciseChange(next.exercise))\n }, [store, onExerciseChange])\n\n function tapStage(pointer: ScreenPoint) {\n if (state.pendingObjectType !== 'point') {\n selectGiven(store, null)\n return\n }\n // Snapped placement records *why* the point is there, not just where: the\n // Point tool is the one gesture where the author asked for this point, so\n // an on-object or intersection snap becomes a derived given that follows\n // its parents (M3.1-6). The connection gestures keep placing free points —\n // `preserveCreatedPointOrigins: false` above — so a drag never attaches a\n // dependency nobody asked for.\n const placement = placementSnap.resolvePlacement(pointer)\n if (placement.snap?.kind === 'point') {\n const pointId = placement.snap.targetIds[0]\n if (pointId) promoteGivenPoint(store, pointId)\n return\n }\n const definition = originForSnap(placement)\n // Student-draggable only when the point is free, which is the rule this\n // gesture had before it could author a derived point at all. Listing a\n // derived given would be inert for an intersection and would hand the\n // student a slide along the host for an on-object point — an affordance no\n // approved row asks for (M3.1-12).\n if (definition) addGivenPoint(store, definition, 'at' in definition)\n }\n\n function selectAndReport(id: string) {\n selectGiven(store, id)\n onGivenSelect?.(id)\n }\n\n usePointerGestures(stageRef, size, store, {\n tool: state.pendingObjectType,\n connection,\n placement: placementSnap,\n pointOnScreen: (id) => {\n const point = points.get(id)\n return point ? mathToScreen(point, viewport, size) : null\n },\n // A derived point is placed by its parents, so it is drawn but not\n // dragged: `moveGivenPoint` refuses the move, and the lifecycle never\n // starts a drag it would refuse (M3.1-1).\n canDragPoint: (id) => isGivenPointDraggable(state, id),\n dragPoint: (id, to) => moveGivenPoint(store, id, screenToMath(to, viewport, size)),\n endPointDrag: () => endGivenPointDrag(store),\n tapPoint: (id) => {\n if (capabilitiesOf(id).selectable) selectAndReport(id)\n },\n tapObject: selectAndReport,\n tapStage,\n abandonConstruction: () => cancelGivenConstruction(store),\n disarmTool: () => selectPendingObjectType(store, null),\n })\n\n return (\n <Stage\n ref={stageRef}\n width={size.width}\n height={size.height}\n style={{\n ...GESTURE_SURFACE_STYLE,\n cursor:\n state.pendingObjectType === 'point' ? 'crosshair' : connecting ? 'crosshair' : 'default',\n }}\n >\n <Layer>\n <Rect width={size.width} height={size.height} fill=\"#fbfcff\" listening={false} />\n <GeometryGrid\n viewport={viewport}\n canvas={size}\n visible={Boolean(state.exercise.showGrid)}\n />\n {connectionPreviewPoints && (\n <KonvaLine\n points={connectionPreviewPoints}\n stroke=\"#786af5\"\n strokeWidth={3}\n dash={[9, 6]}\n lineCap=\"round\"\n listening={false}\n />\n )}\n {objects.map(({ id: objectId, definition }) => {\n // An implicit given is scaffolding: it holds the figure together and\n // is not drawn, here as on the student canvas (ADR 0008 §\n // Presentation). It stays in `objects`, so a placement still snaps\n // onto it.\n const capabilities = capabilitiesOf(objectId)\n if (!capabilities.rendered) return null\n // Drawn through the registry's descriptor, so a given of a kind this\n // canvas has never heard of still renders (M2.8). An object whose\n // points are not all placed is not on screen and draws nothing.\n const resolved = resolveAgainstPoints(definition, points)\n if (!resolved) return null\n return (\n <EntityShape\n key={objectId}\n id={objectId}\n descriptor={shapeOfResolved(resolved)}\n viewport={viewport}\n canvas={size}\n label={labelOf(objectId)}\n selected={\n objectId === state.selectedGivenId || highlightedGivenIds.includes(objectId)\n }\n listening={capabilities.selectable && !state.pendingObjectType}\n />\n )\n })}\n {goalPreviews.map((preview) => {\n if (!preview.shape) return null\n if (preview.shape.type === 'segment') {\n return (\n <SegmentShape\n key={`goal-preview-${preview.id}`}\n id={`goal-preview-${preview.id}`}\n from={preview.shape.from}\n to={preview.shape.to}\n viewport={viewport}\n canvas={size}\n color=\"#f59e0b\"\n strokeWidth={4}\n dashed\n opacity={0.72}\n listening={false}\n />\n )\n }\n if (preview.shape.type === 'point') {\n return (\n <PointShape\n key={`goal-preview-${preview.id}`}\n id={`goal-preview-${preview.id}`}\n position={preview.shape.at}\n viewport={viewport}\n canvas={size}\n color=\"#f59e0b\"\n />\n )\n }\n return (\n <LineShape\n key={`goal-preview-${preview.id}`}\n id={`goal-preview-${preview.id}`}\n through={preview.shape.through}\n viewport={viewport}\n canvas={size}\n color=\"#f59e0b\"\n strokeWidth={3}\n dashed\n opacity={0.72}\n listening={false}\n />\n )\n })}\n {[...points].map(([pointId, point]) => {\n const capabilities = capabilitiesOf(pointId)\n if (!capabilities.rendered) return null\n return (\n <PointShape\n key={pointId}\n id={pointId}\n position={point}\n viewport={viewport}\n canvas={size}\n // The authored label wins; the id is the editor's fallback for a\n // point that has none, so an author can still tell two points\n // apart while building the figure (M2.G.4.b).\n label={labelOf(pointId) ?? pointId}\n selected={\n pointId === state.pendingFirstPointId ||\n pointId === state.selectedGivenId ||\n highlightedGivenIds.includes(pointId)\n }\n connectable={connecting && capabilities.snapTarget}\n snapTarget={pointId === snapTargetId}\n />\n )\n })}\n {placementSnap.snap && (\n <SnapMarker snap={placementSnap.snap} viewport={viewport} canvas={size} />\n )}\n <GeometryInstruments tools={state.exercise.tools} canvas={size} />\n </Layer>\n </Stage>\n )\n}\n","// What an entity is called, on either surface (M1b.3).\n//\n// The canvas drew the fallback inline and the semantic companion would have\n// had to guess the same one: a figure that announces `student-point-1` while\n// it draws `P1` is two figures. So the chain lives here — authored label\n// first (M2.G.4.b), then the id, prettified for the ids the student store\n// generated.\n//\n// The prettifying is about the *store's id scheme*, not about a kind, which is\n// why it is a prefix match and not a table: `student-<kind>-<n>` is what\n// `nextId()` produces, and a kind added to the registry gets a readable name\n// here without an edit.\nimport type { EntityId } from '../geometry/entityModel'\n\n/** Which document declared an entity — the half of naming an id cannot answer. */\nexport type EntityOwner = 'given' | 'student'\n\nconst STUDENT_ID = /^student-(.+)-(\\d+)$/\n\n/**\n * The name both surfaces use for `id`.\n *\n * `owner` decides whether the id is prettified, because only the student store\n * promises the `student-<kind>-<n>` shape. An exercise may author an entity\n * whose id happens to look like one, and rewriting a teacher's chosen id would\n * be the canvas relabelling the exercise.\n */\nexport function entityDisplayName(id: EntityId, label: string | undefined, owner: EntityOwner) {\n if (label) return label\n if (owner === 'given') return id\n const generated = STUDENT_ID.exec(id)\n if (!generated?.[1] || !generated[2]) return id\n // `P1`, the canvas convention for a student's own point, and `segment 1` for\n // everything else — a point is named like a vertex because that is how a\n // figure is talked about, and an object is named after what it is.\n return generated[1] === 'point' ? `P${generated[2]}` : `${generated[1]} ${generated[2]}`\n}\n","import { useRef } from 'react'\nimport { Layer, Line as KonvaLine, Rect, Stage } from 'react-konva'\nimport type { Stage as KonvaStage } from 'konva/lib/Stage'\nimport { mathToScreen, screenToMath, type CanvasSize } from '../coordinates/transform'\nimport type { Store } from '../store/createStore'\nimport { deriveGeometryCanvasFeedback } from '../validation/deriveGeometryCanvasFeedback'\nimport type { GeometryValidationFeedback } from '../validation/types'\nimport {\n addStudentPoint,\n cancelStudentConstruction,\n executeConstruction,\n endStudentPointDrag,\n isStudentOwned,\n isStudentPointDraggable,\n moveStudentPoint,\n pickStudentPoint,\n promoteStudentPoint,\n selectStudentObject,\n selectStudentObjectType,\n type StudentState,\n} from '../store/studentStore'\nimport { resolveAgainstPoints, shapeOfResolved } from '../geometry/entities'\nimport { entryOf } from '../geometry/entityRecord'\nimport { originForSnap } from '../snapping/computeSnap'\nimport { deriveStudentRenderableGeometry } from './deriveRenderableGeometry'\nimport { useStoreState } from '../store/useStoreState'\nimport { EntityShape } from './shapes/EntityShape'\nimport { entityDisplayName } from './entityDisplayName'\nimport { GeometryGrid } from './GeometryGrid'\nimport { PointShape } from './shapes/PointShape'\nimport { useConnectionPreview } from './useConnectionPreview'\nimport { usePlacementSnap } from './usePlacementSnap'\nimport { usePointerGestures } from './usePointerGestures'\nimport { GESTURE_SURFACE_STYLE, type ScreenPoint } from './gesturePolicy'\nimport { SnapMarker } from './shapes/SnapMarker'\nimport { GeometryInstruments } from './GeometryInstruments'\n\nexport interface StudentGeometryCanvasProps {\n store: Store<StudentState>\n size: CanvasSize\n validationFeedback?: GeometryValidationFeedback | null\n focusedGoalId?: string | null\n}\n\n// Colour is never the only carrier of a verdict: incorrect work is drawn\n// dashed and correct work solid, and every point the check touched gains a\n// ring — so unchecked, correct and incorrect differ in shape as well as hue.\nconst studentAnswerColor = '#1683ff'\nconst correctAnswerColor = '#159a68'\nconst incorrectAnswerColor = '#d64555'\n\nfunction feedbackColor(status: 'correct' | 'incorrect' | undefined): string | undefined {\n return status === 'correct'\n ? correctAnswerColor\n : status === 'incorrect'\n ? incorrectAnswerColor\n : undefined\n}\n\nexport function StudentGeometryCanvas({\n store,\n size,\n validationFeedback = null,\n focusedGoalId = null,\n}: StudentGeometryCanvasProps) {\n const state = useStoreState(store)\n const { exercise, geometry } = state\n // Both documents are v2 and neither needs translating. Coordinates are\n // derived, never stored: a point built on an object follows it, and a point\n // that can no longer be placed is simply absent here and so renders nothing.\n const { points, objects, capabilitiesOf, labelOf, isSnapTarget } =\n deriveStudentRenderableGeometry(exercise, geometry)\n const canvasFeedback = deriveGeometryCanvasFeedback(validationFeedback, geometry, focusedGoalId)\n const stageRef = useRef<KonvaStage>(null)\n const connection = useConnectionPreview({\n points,\n viewport: exercise.viewport,\n canvas: size,\n pendingObjectType: state.pendingObjectType,\n pendingFirstPointId: state.pendingFirstPointId,\n connect: (from, to) => {\n if (state.pendingObjectType !== 'segment' && state.pendingObjectType !== 'line') return\n executeConstruction(store, { tool: state.pendingObjectType, from, to })\n },\n pick: (id) => pickStudentPoint(store, id),\n objects,\n isSnapTarget,\n canCreatePoints: true,\n })\n const { connecting, previewPoints, snapTargetId, snapPreview } = connection\n const placementSnap = usePlacementSnap({\n points,\n objects,\n viewport: exercise.viewport,\n canvas: size,\n showGrid: Boolean(exercise.showGrid),\n isSnapTarget,\n active: state.pendingObjectType === 'point',\n })\n\n // Tapping bare canvas drops the current selection, the way every editor\n // does — the delete affordance should never stay armed by accident. With the\n // point tool it also places. A segment or line tap on bare canvas never gets\n // here with anything to build: a tap does not invent endpoints, and the\n // connection has already declined it.\n function tapStage(pointer: ScreenPoint) {\n if (state.selectedObjectId) selectStudentObject(store, null)\n if (state.pendingObjectType !== 'point') return\n // Snapped placement records *why* the point is there, not just where: on\n // an intersection or an object it becomes a derived point that follows its\n // parents. Tapping an existing implicit point promotes that point instead\n // of stacking another one on top of it.\n const placement = placementSnap.resolvePlacement(pointer)\n if (placement.snap?.kind === 'point') {\n const pointId = placement.snap.targetIds[0]\n if (pointId) promoteStudentPoint(store, pointId)\n return\n }\n const definition = originForSnap(placement)\n if (definition) addStudentPoint(store, definition, true)\n }\n\n usePointerGestures(stageRef, size, store, {\n tool: state.pendingObjectType,\n connection,\n placement: placementSnap,\n pointOnScreen: (id) => {\n const point = points.get(id)\n return point ? mathToScreen(point, exercise.viewport, size) : null\n },\n // An intersection point is placed by its parents, so it is shown but not\n // draggable — dragging it would mean nothing.\n canDragPoint: (id) => isStudentPointDraggable(state, id),\n dragPoint: (id, to) => moveStudentPoint(store, id, screenToMath(to, exercise.viewport, size)),\n endPointDrag: () => endStudentPointDrag(store),\n tapPoint: (id) => {\n // Ownership and the capability are two different rules and both apply:\n // a given is the exercise rather than an answer, and an entity the role\n // says is not selectable is not selectable whoever owns it. Picking an\n // endpoint is neither — that is the construction gesture, the\n // connection's to handle, and `snapTarget` governs it.\n const selects = capabilitiesOf(id).selectable && isStudentOwned(state, id)\n selectStudentObject(store, selects ? id : null)\n },\n // Only the student's own work is selectable, so only it listens and only\n // it can be tapped: givens are the exercise, not their answer.\n tapObject: (id) => selectStudentObject(store, id),\n tapStage,\n abandonConstruction: () => cancelStudentConstruction(store),\n // Escape disarms the tool, the same way it does on the teacher canvas.\n disarmTool: () => selectStudentObjectType(store, null),\n })\n\n return (\n <Stage ref={stageRef} width={size.width} height={size.height} style={GESTURE_SURFACE_STYLE}>\n <Layer>\n <Rect width={size.width} height={size.height} fill=\"#fbfcff\" listening={false} />\n <GeometryGrid\n viewport={exercise.viewport}\n canvas={size}\n visible={Boolean(exercise.showGrid)}\n />\n {previewPoints && (\n <KonvaLine\n points={previewPoints}\n stroke=\"#786af5\"\n strokeWidth={3}\n dash={[9, 6]}\n lineCap=\"round\"\n listening={false}\n />\n )}\n {objects.map(({ id: objectId, definition }) => {\n const isStudentConstruction = Object.prototype.hasOwnProperty.call(\n geometry.entities,\n objectId,\n )\n // Scaffolding the student never asked to see — a support entity an\n // engine gesture created, or an implicit given — is not drawn and\n // cannot be selected, but stays in `objects` so a construction can\n // still snap onto it (ADR 0008 § Presentation).\n const capabilities = capabilitiesOf(objectId)\n if (!capabilities.rendered) return null\n const constructionStatus = entryOf(canvasFeedback.constructions, objectId)\n const constructionColor = feedbackColor(constructionStatus)\n // What the object *is* comes from the registry, not from a branch on\n // its stored type: an object that cannot be placed right now resolves\n // to null and draws nothing, and a kind this canvas has never heard\n // of draws itself through its own descriptor (M2.8).\n const resolved = resolveAgainstPoints(definition, points)\n if (!resolved) return null\n // Only the student's own work is selectable: givens are the\n // exercise, not their answer, so they can never be deleted.\n const selectable = capabilities.selectable && isStudentConstruction\n return (\n <EntityShape\n key={objectId}\n id={objectId}\n descriptor={shapeOfResolved(resolved)}\n viewport={exercise.viewport}\n canvas={size}\n label={labelOf(objectId)}\n color={isStudentConstruction ? (constructionColor ?? studentAnswerColor) : undefined}\n dashed={constructionStatus === 'incorrect'}\n selected={selectable && objectId === state.selectedObjectId}\n // A given swallowing the click was how it stayed unselectable\n // before the capability existed. It is not selectable, so it no\n // longer listens either, and a click on one reaches the stage and\n // clears the selection like any other empty-canvas click.\n //\n // Nothing listens while a tool is armed. A press on a drawn\n // object would otherwise be selection rather than construction,\n // and the spot where two segments meet is exactly where a student\n // wants to start the next one — the case RFC 0001 § 11 names when\n // it says a hidden endpoint has to stay a snap target.\n listening={selectable && !state.pendingObjectType}\n />\n )\n })}\n {[...points].map(([pointId, point]) => {\n const capabilities = capabilitiesOf(pointId)\n if (!capabilities.rendered) return null\n const isGivenPoint = Object.prototype.hasOwnProperty.call(exercise.entities, pointId)\n const isStudentPoint = !isGivenPoint\n const pointStatus = entryOf(canvasFeedback.points, pointId)\n const pointColor = feedbackColor(pointStatus)\n return (\n <PointShape\n key={pointId}\n id={pointId}\n position={point}\n viewport={exercise.viewport}\n canvas={size}\n // The authored label wins over both canvas conventions: a given\n // labelled `A₁` by its author is `A₁` here, and only an unlabelled\n // point falls back to the id — or, for the student's own work, to\n // the `P…` the store's ids are not meant to be read as (M2.G.4.b).\n // The chain itself is `entityDisplayName()`, shared with the\n // semantic companion: a figure that announces one name and draws\n // another is two figures (M1b.3).\n label={entityDisplayName(\n pointId,\n labelOf(pointId),\n isGivenPoint ? 'given' : 'student',\n )}\n color={isStudentPoint ? (pointColor ?? studentAnswerColor) : undefined}\n feedbackRingColor={pointColor}\n feedbackRingDashed={pointStatus === 'incorrect'}\n selected={pointId === state.pendingFirstPointId || pointId === state.selectedObjectId}\n connectable={connecting && capabilities.snapTarget}\n snapTarget={pointId === snapTargetId}\n />\n )\n })}\n {(placementSnap.snap ?? snapPreview) && (\n <SnapMarker\n snap={(placementSnap.snap ?? snapPreview)!}\n viewport={exercise.viewport}\n canvas={size}\n />\n )}\n <GeometryInstruments tools={exercise.tools} canvas={size} />\n </Layer>\n </Stage>\n )\n}\n","// The figure, as sentences (M1b.3).\n//\n// A Konva stage is one `<canvas>`: it has no accessible children, nothing to\n// focus, and no text, so no amount of ARIA on it describes the figure inside.\n// The figure is therefore published twice from one source — the stage for\n// pointers and eyes, this for keyboards and screen readers — and both readings\n// come off the same `GeometryContext`.\n//\n// **Derived, never mirrored.** Nothing here keeps an entity list: the ids, the\n// order, the roles and the resolved forms are all the context's, so a figure\n// that draws differently from what it announces is not a state either surface\n// can reach. That is the acceptance criterion the workstream is written around.\n//\n// **No branch on a kind.** What each entity *is* comes from the registry's\n// `describe()`, the same way what it draws comes from `toShape()`. The circle\n// M2.8 added needed no edit here and neither will a ray.\n//\n// A scene is a plain value with no React in it, which is what makes the\n// wording assertable in a unit test — the component below it only renders.\nimport { capabilitiesOf } from '../geometry/entityCapabilities'\nimport { describeDefinition } from '../geometry/entities'\nimport type { EntityId, EntityKind } from '../geometry/entityModel'\nimport { entryOf } from '../geometry/entityRecord'\nimport type { ObjectTypeName } from '../schemas/objectTypeName.schema'\nimport { buildGeometryContext, type IndexedGeometryContext } from '../state/geometryContext'\nimport { exerciseGeometryContext } from '../state/resolvePoints'\nimport type { StudentState } from '../store/studentStore'\nimport type { TeacherEditorState } from '../store/teacherStore'\nimport { deriveGeometryCanvasFeedback } from '../validation/deriveGeometryCanvasFeedback'\nimport type { GeometryValidationFeedback, GoalStatus } from '../validation/types'\nimport { entityDisplayName, type EntityOwner } from './entityDisplayName'\n\nexport interface SemanticEntity {\n readonly id: EntityId\n readonly kind: EntityKind\n readonly owner: EntityOwner\n // What the canvas labels it, so a listener and a viewer name one entity the\n // same way.\n readonly name: string\n // The registry's sentence: how it is constructed, not where it currently is.\n readonly description: string\n // False for an entity the graph cannot place right now — a vanished\n // intersection, a construction whose parent was deleted. Listed and marked\n // rather than dropped: a list that silently shortens is a figure that\n // changed without saying so.\n readonly placed: boolean\n readonly selected: boolean\n // Whether this entity is the surface's to select. A given is the exercise\n // rather than an answer, so a student may not select one; a teacher may.\n readonly selectable: boolean\n // The focused goal's verdict, where a goal named this entity — the same\n // feedback the canvas draws as a ring, in the form a screen reader can hear.\n readonly status: GoalStatus | null\n // The accessible name of this entity's row, assembled once here so that the\n // component renders a string and the wording is pinned by a unit test.\n readonly text: string\n}\n\nexport interface SemanticScene {\n // Each summary line is present even when it has nothing to report, because a\n // line that disappears makes every assertion about it conditional\n // (docs/accessibility.md § The selector contract).\n readonly toolLine: string\n readonly pendingLine: string\n // Null in teacher mode: an exercise being authored has no attempt to check,\n // and \"Answer: not checked\" would be a promise nothing can keep.\n readonly answerLine: string | null\n readonly entities: readonly SemanticEntity[]\n readonly selectedId: EntityId | null\n}\n\n/**\n * The student's figure: the exercise's givens and their own work, with the\n * last check's verdict where it applies.\n *\n * `validationFeedback` and `focusedGoalId` are the canvas's own two props, and\n * the per-entity verdict is derived through the same\n * `deriveGeometryCanvasFeedback()` the canvas colours with — one goal's\n * feedback, so the companion and the canvas can never disagree about which\n * entities a check touched.\n */\nexport function deriveStudentSemanticScene(\n state: StudentState,\n validationFeedback: GeometryValidationFeedback | null = null,\n focusedGoalId: string | null = null,\n): SemanticScene {\n const { exercise, geometry } = state\n const context = buildGeometryContext(exercise, geometry)\n const feedback = deriveGeometryCanvasFeedback(validationFeedback, geometry, focusedGoalId)\n const nameOf = namingFor(context)\n\n const entities = readEntities(context, context.entityIds, {\n nameOf,\n selectedId: state.selectedObjectId,\n // Ownership and the capability are two different rules and both apply,\n // exactly as they do on the student canvas: a given is the exercise, not\n // an answer, so it is never the student's to select.\n isSelectable: (id, selectable) => selectable && !context.isGiven(id),\n statusOf: (id, kind) =>\n entryOf(kind === 'point' ? feedback.points : feedback.constructions, id) ?? null,\n })\n\n return {\n toolLine: toolLine(state.pendingObjectType),\n pendingLine: pendingLine(state.pendingObjectType, state.pendingFirstPointId, nameOf),\n answerLine: answerLine(validationFeedback),\n entities,\n selectedId: state.selectedObjectId,\n }\n}\n\n/**\n * The exercise being authored. Every entity belongs to the exercise, so every\n * drawn one is selectable and none carries a verdict.\n *\n * Walked in declaration order rather than in resolution order, for the reason\n * `deriveRenderableGeometry()` walks it that way: an author reordering the\n * entity record expects both the figure and its description to follow.\n */\nexport function deriveTeacherSemanticScene(state: TeacherEditorState): SemanticScene {\n const context = exerciseGeometryContext(state.exercise)\n const nameOf = namingFor(context)\n\n const entities = readEntities(context, Object.keys(state.exercise.entities), {\n nameOf,\n selectedId: state.selectedGivenId,\n isSelectable: (_id, selectable) => selectable,\n statusOf: () => null,\n })\n\n return {\n toolLine: toolLine(state.pendingObjectType),\n pendingLine: pendingLine(state.pendingObjectType, state.pendingFirstPointId, nameOf),\n answerLine: null,\n entities,\n selectedId: state.selectedGivenId,\n }\n}\n\ninterface ReadOptions {\n nameOf: (id: EntityId) => string\n selectedId: EntityId | null\n isSelectable: (id: EntityId, selectable: boolean) => boolean\n statusOf: (id: EntityId, kind: EntityKind) => GoalStatus | null\n}\n\n// One walk for both modes. What differs between them is which ids, in which\n// order, and the two rules passed in; everything else is the context's answer\n// about an entity and is asked the same way on either side.\nfunction readEntities(\n context: IndexedGeometryContext,\n ids: readonly EntityId[],\n options: ReadOptions,\n): readonly SemanticEntity[] {\n const entities: SemanticEntity[] = []\n\n for (const id of ids) {\n const presentation = context.presentationOf(id)\n // Listed exactly when the canvas draws it. Implicit scaffolding is not\n // drawn, is not announced, and stays snappable — one rule, three readings\n // (ADR 0008 § Presentation).\n if (!capabilitiesOf(presentation.role).rendered) continue\n const definition = context.definitionOf(id)\n const kind = context.kindOf(id)\n if (!definition || !kind) continue\n\n const owner: EntityOwner = context.isGiven(id) ? 'given' : 'student'\n const name = entityDisplayName(id, presentation.label, owner)\n const description = describeDefinition(definition, options.nameOf)\n const placed = context.resolvedOf(id) !== undefined\n const status = options.statusOf(id, kind)\n\n entities.push({\n id,\n kind,\n owner,\n name,\n description,\n placed,\n selected: id === options.selectedId,\n selectable: options.isSelectable(id, capabilitiesOf(presentation.role).selectable),\n status,\n text: rowText(name, description, placed, status),\n })\n }\n\n return entities\n}\n\n// `<name>: <sentence>`, then what a viewer would see instead of read. The\n// suffixes are part of the accessible name on purpose: a check's verdict is\n// drawn as a ring and a colour, and a listener who is never told it has been\n// given the figure without the feedback.\nfunction rowText(\n name: string,\n description: string,\n placed: boolean,\n status: GoalStatus | null,\n): string {\n const suffix = `${placed ? '' : ', not on screen'}${status ? `, ${status}` : ''}`\n return `${name}: ${description}${suffix}`\n}\n\n// The naming the registry's `describe()` resolves parents through, so\n// \"segment from A to B\" names the two points the canvas labelled `A` and `B`.\nfunction namingFor(context: IndexedGeometryContext): (id: EntityId) => string {\n return (id) =>\n entityDisplayName(id, labelOf(context, id), context.isGiven(id) ? 'given' : 'student')\n}\n\n// The same read `deriveRenderableGeometry()` makes: a label is only meaningful\n// on an explicit entity (ADR 0008 § Presentation), and a parent named in a\n// sentence may well be implicit scaffolding.\nfunction labelOf(context: IndexedGeometryContext, id: EntityId): string | undefined {\n const presentation = context.presentationOf(id)\n return presentation.role === 'explicit' ? presentation.label : undefined\n}\n\nfunction toolLine(pendingObjectType: ObjectTypeName | null): string {\n return `Tool: ${pendingObjectType ?? 'none'}`\n}\n\nfunction pendingLine(\n pendingObjectType: ObjectTypeName | null,\n pendingFirstPointId: EntityId | null,\n nameOf: (id: EntityId) => string,\n): string {\n if (!pendingObjectType || !pendingFirstPointId) return 'Building: nothing'\n return `Building: ${pendingObjectType} from ${nameOf(pendingFirstPointId)}`\n}\n\nfunction answerLine(feedback: GeometryValidationFeedback | null): string {\n if (!feedback) return 'Answer: not checked'\n const correct = feedback.result.goals.filter((goal) => goal.status === 'correct').length\n return `Answer: ${correct} of ${feedback.result.goals.length} goals correct`\n}\n\n/**\n * What changed between two scenes, as one sentence to put in a live region —\n * `null` when nothing worth interrupting a construction for did.\n *\n * A pure function of the two scenes rather than a subscription, so what gets\n * announced is assertable without a browser, and so a component can compute it\n * from the scene it already holds.\n *\n * Deliberately silent about selection: the selected row carries\n * `aria-pressed`, and it has focus, so announcing it would say the same thing\n * twice. Also silent about drags — one message per pointer event is noise, and\n * a drag ends in a construction that is announced.\n */\nexport function announceSceneChange(\n previous: SemanticScene | null,\n next: SemanticScene,\n): string | null {\n // Nothing to compare against on first render, and announcing the initial\n // figure would talk over a student who has only just arrived.\n if (!previous) return null\n\n const before = new Map(previous.entities.map((entity) => [entity.id, entity]))\n const after = new Map(next.entities.map((entity) => [entity.id, entity]))\n const added = next.entities.filter((entity) => !before.has(entity.id))\n const removed = previous.entities.filter((entity) => !after.has(entity.id))\n\n const parts: string[] = []\n if (added.length === 1 && added[0]) parts.push(`Added ${added[0].text}`)\n else if (added.length > 1) parts.push(`Added ${added.length} objects`)\n if (removed.length === 1 && removed[0]) parts.push(`Removed ${removed[0].text}`)\n else if (removed.length > 1) parts.push(`Removed ${removed.length} objects`)\n\n // The verdict, when it is a new one. `answerLine` already reads as a\n // sentence, so it is announced as it stands rather than reworded.\n if (next.answerLine && next.answerLine !== previous.answerLine) parts.push(next.answerLine)\n // Last, and only when nothing else happened: arming a tool is the least of\n // what a student needs to hear about a commit that also built something.\n if (parts.length === 0 && next.toolLine !== previous.toolLine) parts.push(next.toolLine)\n\n return parts.length ? `${parts.join('. ')}.` : null\n}\n","// The semantic DOM companion to the canvas (M1b.3).\n//\n// A sibling of the stage rather than a wrapper around it: making the canvas\n// component render one would put a DOM box around a stage whose every\n// pixel-exact test measures from it, and a host that lays the two out itself\n// can put the companion wherever its design wants it — beside the figure, in a\n// drawer, or visually hidden.\n//\n// Presentational. Everything it says was decided in `deriveSemanticScene.ts`,\n// which is where the wording is pinned; what is left here is the markup, the\n// live region, and turning a click into the host's selection.\n//\n// The two wrappers below are the whole wiring for either store, so a consumer\n// mounts one element next to their canvas and is done.\nimport { useEffect, useRef, useState } from 'react'\nimport type { EntityId } from '../geometry/entityModel'\nimport type { Store } from '../store/createStore'\nimport { selectGiven, type TeacherEditorState } from '../store/teacherStore'\nimport { selectStudentObject, type StudentState } from '../store/studentStore'\nimport { useStoreState } from '../store/useStoreState'\nimport type { GeometryValidationFeedback } from '../validation/types'\nimport {\n announceSceneChange,\n deriveStudentSemanticScene,\n deriveTeacherSemanticScene,\n type SemanticScene,\n} from './deriveSemanticScene'\n\nexport interface GeometryCompanionProps {\n scene: SemanticScene\n // Selecting the already-selected entity clears the selection, which is what\n // a second click on the canvas does.\n onSelect: (id: EntityId | null) => void\n // The region's accessible name. Defaulted rather than required, because it\n // is half of the selector contract M1b.4 drives these from\n // (docs/accessibility.md), and a host that mounts two companions on one page\n // is the case that needs to override it.\n label?: string\n className?: string\n}\n\nexport function GeometryCompanion({\n scene,\n onSelect,\n label = 'Figure contents',\n className,\n}: GeometryCompanionProps) {\n const announcement = useSceneAnnouncement(scene)\n\n return (\n <section aria-label={label} className={className}>\n <p>{scene.toolLine}</p>\n <p>{scene.pendingLine}</p>\n <ul aria-label=\"Figure objects\">\n {scene.entities.length === 0 && <li>Nothing has been drawn yet.</li>}\n {scene.entities.map((entity) =>\n entity.selectable ? (\n <li key={entity.id}>\n {/* The keyboard's way onto the figure: the same selection a click\n on the stage makes, so every control already wired to it —\n Delete, the host's delete button, the property card — works\n unchanged. */}\n <button\n type=\"button\"\n aria-pressed={entity.selected}\n onClick={() => onSelect(entity.selected ? null : entity.id)}\n >\n {entity.text}\n </button>\n </li>\n ) : (\n // Not a disabled button: a given is not the student's to select,\n // but it is theirs to read, and a disabled control is skipped by\n // exactly the navigation this exists for.\n <li key={entity.id}>{entity.text}</li>\n ),\n )}\n </ul>\n {scene.answerLine !== null && <p>{scene.answerLine}</p>}\n {/* Polite, and atomic so a verdict is read as one sentence. Nothing here\n interrupts: the student is mid-construction, and an assertive region\n would talk over their own work.\n\n The message is a keyed child, because a live region announces DOM\n changes and not string inequality: placing two identical points in a\n row produces the same sentence twice, and without a node to replace\n the second one is never spoken. */}\n <div role=\"status\" aria-live=\"polite\" aria-atomic=\"true\">\n <span key={announcement.tick}>{announcement.message}</span>\n </div>\n </section>\n )\n}\n\nexport interface StudentGeometryCompanionProps {\n store: Store<StudentState>\n validationFeedback?: GeometryValidationFeedback | null\n focusedGoalId?: string | null\n label?: string\n className?: string\n}\n\n/** The companion for a student attempt — the same two props the canvas takes. */\nexport function StudentGeometryCompanion({\n store,\n validationFeedback = null,\n focusedGoalId = null,\n label,\n className,\n}: StudentGeometryCompanionProps) {\n const state = useStoreState(store)\n const scene = deriveStudentSemanticScene(state, validationFeedback, focusedGoalId)\n\n return (\n <GeometryCompanion\n scene={scene}\n onSelect={(id) => selectStudentObject(store, id)}\n label={label}\n className={className}\n />\n )\n}\n\nexport interface TeacherGeometryCompanionProps {\n store: Store<TeacherEditorState>\n label?: string\n className?: string\n}\n\n/** The companion for an exercise being authored. */\nexport function TeacherGeometryCompanion({\n store,\n label,\n className,\n}: TeacherGeometryCompanionProps) {\n const state = useStoreState(store)\n const scene = deriveTeacherSemanticScene(state)\n\n return (\n <GeometryCompanion\n scene={scene}\n onSelect={(id) => selectGiven(store, id)}\n label={label}\n className={className}\n />\n )\n}\n\n// The live region's text, from the scene the component already holds.\n//\n// In an effect rather than during render, because comparing against a ref while\n// rendering is a lie under concurrent React: a render that is thrown away would\n// still have consumed the previous scene, and the announcement it computed\n// would never be spoken. The first pass sets the baseline and says nothing.\nfunction useSceneAnnouncement(scene: SemanticScene): { message: string; tick: number } {\n const previous = useRef<SemanticScene | null>(null)\n const [announcement, setAnnouncement] = useState({ message: '', tick: 0 })\n\n useEffect(() => {\n const message = announceSceneChange(previous.current, scene)\n previous.current = scene\n if (message !== null) setAnnouncement((current) => ({ message, tick: current.tick + 1 }))\n }, [scene])\n\n return announcement\n}\n","// The controlled-input half of `StudentGeometryExercise` (RFC 0005 § Decision\n// 2, ADR 0014), kept free of React and the DOM so it is tested against real\n// stores. Internal: not exported from any entry point.\nimport type { GeometryExercise } from '../schemas/exercise.schema'\nimport type { GeometryState } from '../schemas/geometryState.schema'\nimport type { Store } from '../store/createStore'\nimport {\n createStudentStore,\n replaceStudentAttempt,\n replaceStudentSession,\n type ConstructionNotice,\n type StudentGeometryChangeMeta,\n type StudentState,\n} from '../store/studentStore'\nimport type { CheckGeometryAnswerResult } from '../validation/checkGeometryAnswer'\nimport { loadExerciseDocument } from '../validation/exerciseLoad'\nimport { createEmptyGeometryAttempt } from '../validation/geometryAttemptStatus'\nimport {\n fingerprintGeometryAttempt,\n fingerprintGeometryExercise,\n} from '../validation/gradingFingerprints'\nimport type { GeometryValidationFeedback } from '../validation/types'\nimport { validateGeometryStateInput } from '../validation/validateGeometryStateInput'\n\n/** Why the component cannot mount: `source` names the prop at fault. */\nexport interface StudentGeometryExerciseIssue {\n source: 'exercise' | 'value'\n path: Array<string | number>\n message: string\n}\n\nexport type StudentExerciseInputs =\n | {\n success: true\n exercise: GeometryExercise\n attempt: GeometryState\n exerciseFingerprint: string\n attemptFingerprint: string\n }\n | { success: false; issues: StudentGeometryExerciseIssue[] }\n\n/**\n * Both props, loaded the way Check loads them: the exercise through the\n * solution-free grading load, so a malformed authored solution never stops a\n * student working (ADR 0015), and the value normalized against that exercise.\n * An absent value — `undefined` or `null` — is a fresh attempt.\n */\nexport function loadStudentExerciseInputs(\n exerciseInput: unknown,\n valueInput: unknown,\n): StudentExerciseInputs {\n const exercise = loadExerciseDocument(exerciseInput)\n if (!exercise.success) {\n return {\n success: false,\n issues: exercise.errors.map((issue) => ({ source: 'exercise', ...issue })),\n }\n }\n const attempt =\n valueInput == null\n ? ({ success: true, data: createEmptyGeometryAttempt() } as const)\n : validateGeometryStateInput(valueInput, exercise.data)\n if (!attempt.success) {\n return {\n success: false,\n issues: attempt.errors.map((issue) => ({ source: 'value', ...issue })),\n }\n }\n return {\n success: true,\n exercise: exercise.data,\n attempt: attempt.data,\n exerciseFingerprint: fingerprintGeometryExercise(exercise.data),\n attemptFingerprint: fingerprintGeometryAttempt(attempt.data),\n }\n}\n\n// The attempt as grading reads it, so a store's own geometry and a check\n// result's fingerprint are taken over the same normalized document.\nfunction attemptFingerprint(exercise: GeometryExercise, geometry: GeometryState): string | null {\n const normalized = validateGeometryStateInput(geometry, exercise)\n return normalized.success ? fingerprintGeometryAttempt(normalized.data) : null\n}\n\n/**\n * One mounted component's store, and the fingerprints of the inputs it last\n * applied. The value fingerprint is what makes a value \"arrive\": only a value\n * that differs from the last one applied is compared with the store at all, so\n * a host that never passes one does not wipe the student's work on re-render.\n */\nexport interface StudentExerciseSession {\n store: Store<StudentState>\n exercise: string\n value: string\n}\n\nexport function createStudentExerciseSession(\n inputs: Extract<StudentExerciseInputs, { success: true }>,\n): StudentExerciseSession {\n return {\n store: createStudentStore(inputs.exercise, inputs.attempt),\n exercise: inputs.exerciseFingerprint,\n value: inputs.attemptFingerprint,\n }\n}\n\n/**\n * Applies newly loaded props to the session's store. A different exercise\n * replaces exercise and attempt together; a different value replaces the\n * attempt, unless it is the store's own attempt handed back — an echo, which\n * keeps history. Neither replacement records a receipt, so neither reaches\n * `onChange`.\n */\nexport function syncStudentExerciseSession(\n session: StudentExerciseSession,\n inputs: Extract<StudentExerciseInputs, { success: true }>,\n): void {\n if (inputs.exerciseFingerprint !== session.exercise) {\n session.exercise = inputs.exerciseFingerprint\n session.value = inputs.attemptFingerprint\n replaceStudentSession(session.store, inputs.exercise, inputs.attempt)\n return\n }\n if (inputs.attemptFingerprint === session.value) return\n session.value = inputs.attemptFingerprint\n const { exercise, geometry } = session.store.getState()\n if (attemptFingerprint(exercise, geometry) !== inputs.attemptFingerprint) {\n replaceStudentAttempt(session.store, inputs.attempt)\n }\n}\n\nexport interface StudentExerciseListeners {\n onChange(attempt: GeometryState, meta: StudentGeometryChangeMeta): void\n onNotice?(notice: ConstructionNotice): void\n}\n\n/**\n * One `onChange` per completed-action receipt and one `onNotice` per refused\n * construction, read off the store's transitions rather than off renders. The\n * store writes a new notice object for every refusal, so a repeated one is\n * reported again. `listeners` is read on each event, so a host's callbacks may\n * change identity freely.\n */\nexport function subscribeStudentExerciseEvents(\n store: Store<StudentState>,\n listeners: () => StudentExerciseListeners,\n): () => void {\n return store.subscribe((next, previous) => {\n const receipt = next.lastAttemptChange\n if (receipt && receipt !== previous.lastAttemptChange) {\n // A copy: a host that edits what it was given must not edit the store.\n listeners().onChange(JSON.parse(JSON.stringify(next.geometry)) as GeometryState, {\n action: receipt.action,\n entityIds: [...receipt.entityIds],\n })\n }\n const notice = next.constructionNotice\n if (notice && notice !== previous.constructionNotice) listeners().onNotice?.(notice)\n })\n}\n\n/**\n * The feedback a check result carries, while it still describes what is on the\n * canvas: both fingerprints must equal the current ones. Anything else — a\n * failed check, an edit, a reset, a replacement — shows no feedback.\n */\nexport function currentFeedback(\n result: CheckGeometryAnswerResult | null | undefined,\n exercise: GeometryExercise,\n geometry: GeometryState,\n): GeometryValidationFeedback | null {\n if (!result?.success) return null\n if (result.fingerprints.exercise !== fingerprintGeometryExercise(exercise)) return null\n return result.fingerprints.attempt === attemptFingerprint(exercise, geometry) ? result.data : null\n}\n","import type { ObjectTypeName } from '../schemas/objectTypeName.schema'\n\nexport interface ToolbarProps {\n selected: ObjectTypeName | null\n onSelect: (type: ObjectTypeName | null) => void\n available?: ObjectTypeName[]\n}\n\nconst OBJECT_TYPES: ObjectTypeName[] = ['point', 'segment', 'line']\n\n// The 44 CSS px minimum activation box (RFC 0004 § Decision 3), carried by the\n// component rather than by a host stylesheet: the library ships no CSS, and a\n// tool button that only reaches finger size once dev-page/styles.css is loaded\n// is not a target the engine can promise. A host still owns the rest of the\n// look — the box is a floor, not a size.\nconst MINIMUM_TARGET_STYLE = { minWidth: 44, minHeight: 44 } as const\n\n// Default/convenience UI, not part of the required contract — a host is\n// free to build its own chrome calling teacherStore's actions directly\n// (addGivenSegment, addGivenLine, setTools, addGoal...) without ever\n// rendering this component. Instruments and goal authoring deliberately\n// live in host UI because they are exercise configuration, not object types.\nexport function Toolbar({ selected, onSelect, available = OBJECT_TYPES }: ToolbarProps) {\n return (\n <div role=\"toolbar\" aria-label=\"Geometry tools\">\n {available.map((type) => (\n <button\n key={type}\n type=\"button\"\n aria-pressed={selected === type}\n style={MINIMUM_TARGET_STYLE}\n onClick={() => onSelect(selected === type ? null : type)}\n >\n {type}\n </button>\n ))}\n </div>\n )\n}\n","import { useLayoutEffect, useMemo, useRef, useState } from 'react'\nimport { entryOf } from '../geometry/entityRecord'\nimport type { GeometryState } from '../schemas/geometryState.schema'\nimport type { Store } from '../store/createStore'\nimport {\n deleteStudentObject,\n redoStudentChange,\n resetStudentAttempt,\n selectStudentObjectType,\n setStudentReadOnly,\n undoStudentChange,\n type ConstructionNotice,\n type StudentGeometryChangeMeta,\n type StudentState,\n} from '../store/studentStore'\nimport { useStoreState } from '../store/useStoreState'\nimport type { CheckGeometryAnswerResult } from '../validation/checkGeometryAnswer'\nimport { entityDisplayName } from './entityDisplayName'\nimport { StudentGeometryCompanion } from './GeometryCompanion'\nimport { StudentGeometryCanvas } from './StudentGeometryCanvas'\nimport {\n createStudentExerciseSession,\n currentFeedback,\n loadStudentExerciseInputs,\n subscribeStudentExerciseEvents,\n syncStudentExerciseSession,\n type StudentExerciseSession,\n type StudentGeometryExerciseIssue,\n} from './studentExerciseSession'\nimport { Toolbar } from './Toolbar'\n\nexport type { StudentGeometryExerciseIssue } from './studentExerciseSession'\n\n/** Which built-in controls are shown. Each defaults to `true`; hiding one changes no permission. */\nexport interface StudentGeometryControls {\n toolbar?: boolean\n history?: boolean\n delete?: boolean\n reset?: boolean\n}\n\nexport interface StudentGeometryExerciseProps {\n exercise: unknown\n value?: unknown\n onChange(attempt: GeometryState, meta: StudentGeometryChangeMeta): void\n readOnly?: boolean\n validationFeedback?: CheckGeometryAnswerResult | null\n focusedGoalId?: string | null\n controls?: StudentGeometryControls\n className?: string\n onNotice?(notice: ConstructionNotice): void\n onError?(issues: StudentGeometryExerciseIssue[]): void\n}\n\n// The 44 CSS px floor `Toolbar` carries (RFC 0004 § Decision 3).\nconst TARGET_STYLE = { minWidth: 44, minHeight: 44 } as const\nconst FIELDSET_STYLE = { border: 0, margin: 0, padding: 0, minWidth: 0 } as const\n\n/**\n * A complete, mobile-first student exercise for a host application (RFC 0005,\n * ADR 0014): tools, history, delete, reset, the construction notice, a canvas\n * that measures its own container and the semantic companion. The host keeps\n * Check, Reveal, persistence and result wording.\n *\n * Controlled: `exercise` and `value` are untrusted and parsed on the way in,\n * every completed student action reaches `onChange` once, and a different\n * value or exercise replaces the attempt or the whole session on the same\n * store without echoing. Every mount owns its store.\n */\nexport function StudentGeometryExercise(props: StudentGeometryExerciseProps) {\n const {\n exercise,\n value,\n readOnly = false,\n validationFeedback = null,\n focusedGoalId = null,\n controls,\n className,\n } = props\n // Loaded on every render, never memoized on the props' identity: a host may\n // change either object in place, and content decides replacement.\n const inputs = loadStudentExerciseInputs(exercise, value)\n const [session, setSession] = useState<StudentExerciseSession | null>(null)\n // The host's callbacks as of the last commit, read when an event fires.\n const callbacks = useRef(props)\n const reportedIssues = useRef<string | null>(null)\n\n // Created from the first valid inputs rather than at mount, so invalid input\n // never produces a store; afterwards the same store is kept.\n if (!session && inputs.success) setSession(createStudentExerciseSession(inputs))\n\n useLayoutEffect(() => {\n callbacks.current = props\n })\n\n useLayoutEffect(() => {\n if (session && inputs.success) syncStudentExerciseSession(session, inputs)\n }, [inputs, session])\n\n useLayoutEffect(\n () =>\n session ? subscribeStudentExerciseEvents(session.store, () => callbacks.current) : undefined,\n [session],\n )\n\n useLayoutEffect(() => {\n if (session) setStudentReadOnly(session.store, readOnly)\n }, [session, readOnly])\n\n // Once per distinct issue list, not once per host render.\n useLayoutEffect(() => {\n const key = inputs.success ? null : JSON.stringify(inputs.issues)\n if (!inputs.success && key !== reportedIssues.current)\n callbacks.current.onError?.(inputs.issues)\n reportedIssues.current = key\n }, [inputs])\n\n if (!inputs.success) {\n return (\n <div className={className} role=\"alert\">\n This exercise cannot be displayed.\n </div>\n )\n }\n if (!session) return null\n return (\n <StudentExerciseView\n store={session.store}\n validationFeedback={validationFeedback}\n focusedGoalId={focusedGoalId}\n controls={controls}\n className={className}\n />\n )\n}\n\nfunction StudentExerciseView({\n store,\n validationFeedback,\n focusedGoalId,\n controls,\n className,\n}: {\n store: Store<StudentState>\n validationFeedback: CheckGeometryAnswerResult | null\n focusedGoalId: string | null\n controls: StudentGeometryControls | undefined\n className: string | undefined\n}) {\n const state = useStoreState(store)\n const { exercise, geometry, selectedObjectId, constructionNotice } = state\n const feedback = useMemo(\n () => currentFeedback(validationFeedback, exercise, geometry),\n [validationFeedback, exercise, geometry],\n )\n const goalId = feedback ? focusedGoalId : null\n const slot = useRef<HTMLDivElement>(null)\n const [size, setSize] = useState({ width: 0, height: 0 })\n const [xMin, yMin, xMax, yMax] = exercise.viewport\n\n // Measured for as long as the component is mounted, so a host that resizes\n // the container later — a focus mode, a rotation — redraws the figure.\n useLayoutEffect(() => {\n const element = slot.current\n if (!element) return\n const measure = ({ width, height }: { width: number; height: number }) => {\n const next = { width: Math.floor(width), height: Math.floor(height) }\n setSize((current) =>\n current.width === next.width && current.height === next.height ? current : next,\n )\n }\n measure(element.getBoundingClientRect())\n const observer = new ResizeObserver(([entry]) => entry && measure(entry.contentRect))\n observer.observe(element)\n return () => observer.disconnect()\n }, [])\n\n const shows = (control: keyof StudentGeometryControls) => controls?.[control] !== false\n const notice =\n constructionNotice &&\n `This construction already exists: ${entityDisplayName(\n constructionNotice.entityId,\n entryOf(geometry.presentation, constructionNotice.entityId)?.label,\n 'student',\n )}`\n\n return (\n <div className={className}>\n {/* A disabled fieldset disables every control in it, so a lock is one\n attribute; the store enforces it either way. */}\n <fieldset disabled={state.readOnly} style={FIELDSET_STYLE}>\n {shows('toolbar') && (\n <Toolbar\n selected={state.pendingObjectType}\n available={exercise.allowedObjects}\n onSelect={(type) => selectStudentObjectType(store, type)}\n />\n )}\n {shows('history') && (\n <>\n <button\n type=\"button\"\n style={TARGET_STYLE}\n disabled={!state.past.length}\n onClick={() => undoStudentChange(store)}\n >\n Undo\n </button>\n <button\n type=\"button\"\n style={TARGET_STYLE}\n disabled={!state.future.length}\n onClick={() => redoStudentChange(store)}\n >\n Redo\n </button>\n </>\n )}\n {shows('delete') && (\n <button\n type=\"button\"\n style={TARGET_STYLE}\n disabled={!selectedObjectId}\n onClick={() => selectedObjectId && deleteStudentObject(store, selectedObjectId)}\n >\n Delete\n </button>\n )}\n {shows('reset') && (\n <button type=\"button\" style={TARGET_STYLE} onClick={() => resetStudentAttempt(store)}>\n Reset\n </button>\n )}\n </fieldset>\n {/* The slot takes the container's width at the viewport's aspect ratio;\n the stage is laid over it, so the stage never sizes what measures it. */}\n <div\n ref={slot}\n style={{\n position: 'relative',\n width: '100%',\n aspectRatio: `${xMax - xMin} / ${yMax - yMin}`,\n }}\n >\n {size.width > 0 && size.height > 0 && (\n <div style={{ position: 'absolute', inset: 0 }}>\n <StudentGeometryCanvas\n store={store}\n size={size}\n validationFeedback={feedback}\n focusedGoalId={goalId}\n />\n </div>\n )}\n </div>\n {/* Always in the DOM, so a notice is announced when it appears. */}\n <p role=\"status\">{notice}</p>\n <StudentGeometryCompanion\n store={store}\n validationFeedback={feedback}\n focusedGoalId={goalId}\n />\n </div>\n )\n}\n"],"mappings":"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"}
|