Author name: Joylynne Grace C. Esportuno
Reviewers: Jethro Lagmay
Creation Date: April 8, 2026
References: https://github.com/wyzlab/WyzQuests/issues/54
Status: Approved and Merged
INTRODUCTION & GOALS
Problem Summary
Node validation prevents incomplete quest content from moving into user-facing states. The main validation path scans canvas_metadata.nodes before a creator submits or publishes a quest and before a reviewer approves it. If required node fields are missing, the system returns structured validation errors and blocks the workflow.
Goals & Non-Goals
Goals
Validate required fields for supported visual-canvas node types.
Return structured errors that identify the node, node type, and human-readable issue.
Block creator submission/publication when quest content is incomplete.
Block reviewer approval when quest content is incomplete.
Validate learner quiz submissions before grading in non-preview, non-SCORM learner flows.
Keep validation logic centralized under
lib/validation.
Non-Goals
It does not validate preview or SCORM quiz submissions through the learner validation API.
It does not mutate or repair invalid quest content.
Glossary
Canvas metadata — Serialized quest graph data stored on the quest record, including nodes and edges.
Node validation error — A structured
ValidationErrorreturned byvalidateQuestContent()Visual Canvas Nodes — A content node in the quest editor, such as Text, Image, Quiz, Discussion, Scenario, etc.
HIGH-LEVEL ARCHITECTURE
System Diagram



Technologies used
TypeScript, Next.js, Zod, Sonner toast, Supabase, React/React Flow
DETAILED DESIGN & IMPLEMENTATION
Data Model/Schema
No new database tables or columns are introduced for node validation. The validator reads the existing quest
canvas_metadatafield from Supabase.The node payload shapes are modeled in
lib/schemas/nodes.schema.tstextNodeDataSchemaimageNodeDataSchemavideoNodeDataSchemacodeNodeDataSchemafileNodeDataSchemalinkNodeDataSchemaaudioNodeDataSchemareflectionNodeDataSchemaquestionNodeDataSchemaquizNodeDataSchemadiscussionNodeDataSchemascenarionodeDataSchemachildNodeSchemaAllNodeData
The primary quest validation error contract is:
export interface ValidationError { nodeId: string; nodeLabel: string; edgeLabel?: string; nodeType: string; edgeId?: string; error: string;}
Current
edgeLabelandedgeIdfields are reserved but not populated byvalidateQuestContent()The learner quiz submission error contract is:
export interface QuizSubmissionError { questionId: string; error: string;}
API Specification
#### POST `/api/creator/validate-quest-content` **Authentication:** Required via `authenticateUser()`. **Request body** ```json{ "quest_id": "uuid"}``` **Success response** ```json{ "success": true, "message": "Quest validation passed"}``` **Validation failure response** ```json{ "success": false, "errors": [ { "nodeId": "node-id", "nodeLabel": "Text Node 1", "nodeType": "Text", "error": "Text content is empty" } ], "message": "Cannot submit - Text Node 1: Text content is empty"}``` **Other failures** - Missing `quest_id`: `400` via `ApiResponseHelper.badRequest("quest_id is required")`.- Missing quest: `404` via `ApiResponseHelper.notFound("Quest not found")`.- Unexpected failure: `500` via `ApiResponseHelper.internalError("Validation failed")`. #### PATCH `/api/reviewer/update-quest-status` **Authentication:** Requires an authenticated user and role `REVIEWER` or `ADMIN`. When `publishing_status === "approved"`, the route fetches `canvas_metadata`, calls `validateQuestContent()`, and blocks approval when errors exist. **Validation failure shape** ```json{ "success": false, "message": "Cannot approve - Text Node 1: Text content is empty", "error": { "details": { "errors": [ { "nodeId": "node-id", "nodeLabel": "Text Node 1", "nodeType": "Text", "error": "Text content is empty" } ] } }}``` Exact envelope fields depend on `ApiResponseHelper.badRequest()`. #### POST `/api/learner/validate-quiz-submission` **Authentication:** No explicit authentication in the route. **Request body** ```json{ "questions": [], "answers": {}}``` **Success response** Uses `ApiResponseHelper.success("Submission is valid")`. **Validation failure response** Uses `ApiResponseHelper.validationError("Invalid submission", errors)`, where `errors` is a `QuizSubmissionError[]`.
INFRASTRUCTURE & OPERATIONS
Logic & Workflows
Quest Content Validation
Implemented in lib/validation/quest-validation.ts.
validateQuestContent(canvasMetadata)starts with an emptyValidationError[].If
canvasMetadatais missing, it returnsQuest has no content.If
canvasMetadata.nodesis missing or empty, it returnsQuest has no content nodes.It iterates top-level nodes and builds labels in the format
{NodeType} Node {Number}.It calls private
validateNode()for each top-level node.If a Scenario node has children,
validateNode()recursively validates each child and labels children as{ScenarioLabel} > {ChildType} {Number}.If the canvas has more than one node and no edges, it adds
Node is not connected to any other nodefor each node.The API formats the result with
formatValidationErrors(), which includes up to three detailed errors and then summarizes the remaining count.
Node Rules
Node Type | Required data | Error text |
|---|---|---|
Canvas |
|
|
Canvas |
|
|
Unknown | Node data and type exist |
|
Text |
|
|
Image |
|
|
Video |
|
|
Code |
|
|
Discussion (Internal) |
|
|
Discussion (External) |
|
|
Discussion |
|
|
File |
|
|
Link |
|
|
Audio |
|
|
Reflection |
|
|
Question |
|
|
Question |
|
|
Question | Every choice has text |
|
Quiz (Hub source) |
|
|
Quiz (Custom source) |
|
|
Quiz question |
|
|
Quiz question | Every choice with a |
|
Scenario |
|
|
Multi-node canvas | Edges exist when there is more than one top-level node |
|
Creator Submit / Publish Workflow
Implemented in hooks/quest-editor/useEditorActions.ts.
User changes quest status to
submittedorpublished.The hook saves the canvas through
saveCanvasNow()first.The hook posts
{ quest_id }to/api/creator/validate-quest-content.If validation fails,
toast.error(validateData.message || "Cannot Submit: Quest has empty nodes.")is shown and the status change stops.If validation passes and the new status is
published, the hook creates forum posts for Discussion nodes.The hook then calls
/api/creator/update-questto apply the status change.
Reviewer Approval Workflow
Implemented in app/api/reviewer/update-quest-status/route.ts.
Request body is validated by
updateQuestStatusSchema.The route fetches
creator_id,title, andcanvas_metadata.If
publishing_statusisapproved, it callsvalidateQuestContent().If validation errors exist, it returns a bad request and does not update the quest status.
If validation passes, it updates
quests.publishing_statusand sends notifications.
Learner Quiz Submission Workflow
Implemented across components/learner/renderers/QuizRenderer.tsx, app/api/learner/validate-quiz-submission/route.ts, and lib/validation/quiz-submission-validation.ts.
Learner submits a quiz.
In non-preview and non-SCORM mode,
QuizRendererposts displayed questions and answers to/api/learner/validate-quiz-submission.The API validates payload shape, then calls
validateQuizSubmission().Missing answers become field-level errors.
Essay answers enforce
minWordsandmaxWordswhen configured.Image-map answers must be an object with numeric
xandy.The renderer maps returned errors by
questionId, highlights invalid cards, and scrolls to the first invalid question.If validation passes, grading proceeds through
gradeQuizQuestion().
TESTING & QUALITY ASSURANCE
Dependencies
Dependency | Usage |
|---|---|
Supabase | Reads |
Auth helpers |
|
API response helper | Standardizes non-custom API error envelopes. |
Canvas save layer | Creator flow saves the latest canvas before validation. |
Monitoring & Alerting
There is no dedicated monitoring or alerting for validation failures.
Current observable signals:
Creator validation route logs unexpected exceptions with
console.error("Validation error:", error).Reviewer route logs database update errors with
console.error("Database update error:", error).Learner quiz validation route returns server errors through
ApiResponseHelper.internalError().
Recommended future instrumentation:
Count validation failures by route and node type.
Track top validation error messages to identify confusing editor UX.
Alert on elevated
500responses from validation endpoints.Add structured logs with
quest_id, route, and error count, excluding learner answer payloads.
Deployment Plan
No database migration or feature flag is required for the current implementation.
Safe rollout checklist:
Deploy validation library and API route changes together.
Verify creator submit and publish flows against quests with valid and invalid nodes.
Verify reviewer approval rejects incomplete quests.
Verify learner quiz submissions reject missing answers in standard learner mode.
Confirm preview and SCORM behavior remains unchanged.
Testing & Quality Assurance
Test Strategy
Recommended tests for this feature:
Unit tests for
validateQuestContent()covering every supported node type.Unit tests for Scenario child recursion and labels.
Unit tests for
formatValidationErrors()with one, three, and more than three errors.API tests for
/api/creator/validate-quest-contentsuccess, missingquest_id, missing quest, and invalid content.API tests for reviewer approval blocking invalid quests.
Unit/API tests for
validateQuizSubmission()covering missing answers, choice answers, essay word limits, and image-map answer shape.Component tests for
QuizRenderererror mapping and scroll behavior.
Current related tests in the repository cover validation patterns broadly, but no direct test file for lib/validation/quest-validation.ts was found during this documentation pass.
Known Limitations
Archived nodes are still validated because
validateQuestContent()does not skipdata.isArchived.The learner quiz validation endpoint does not explicitly authenticate users.
Learner quiz submission validation is skipped for preview and SCORM mode.
MAINTENANCE & SUPPORT
Troubleshooting
Symptom | Likely cause | Where to check |
|---|---|---|
Creator sees |
|
|
Creator fixes a node but still sees the old error | Canvas save failed or status flow validated stale metadata. |
|
Reviewer cannot approve a quest | Approval path runs the same quest validator and found invalid content. |
|
All nodes show disconnected errors | Quest has more than one top-level node and no saved edges. |
|
Scenario child errors are hard to locate | Errors are labeled with parent and child type counters, not the child display label. | Scenario |
Learner quiz submission highlights questions |
|
|
Learner quiz validates in production but not preview | Preview intentionally skips the validation endpoint. |
|
Changelog
1.0, Draft, Initial internal technical guide for node validation errors under
lib/validation, 06/22/2026.
Document version: 1.0, Draft, Initial internal technical guide for node validation errors under lib/validation, 06/22/2026.