[7.4] Validation Error

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 ValidationError returned by validateQuestContent()

  • 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_metadata field from Supabase.

  • The node payload shapes are modeled in lib/schemas/nodes.schema.ts

    • textNodeDataSchema

    • imageNodeDataSchema

    • videoNodeDataSchema

    • codeNodeDataSchema

    • fileNodeDataSchema

    • linkNodeDataSchema

    • audioNodeDataSchema

    • reflectionNodeDataSchema

    • questionNodeDataSchema

    • quizNodeDataSchema

    • discussionNodeDataSchema

    • scenarionodeDataSchema

    • childNodeSchema

    • AllNodeData

  • The primary quest validation error contract is:

export interface ValidationError {
nodeId: string;
nodeLabel: string;
edgeLabel?: string;
nodeType: string;
edgeId?: string;
error: string;
}
  • Current edgeLabel and edgeId fields are reserved but not populated by validateQuestContent()

  • 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.

  1. validateQuestContent(canvasMetadata) starts with an empty ValidationError[].

  2. If canvasMetadata is missing, it returns Quest has no content.

  3. If canvasMetadata.nodes is missing or empty, it returns Quest has no content nodes.

  4. It iterates top-level nodes and builds labels in the format {NodeType} Node {Number}.

  5. It calls private validateNode() for each top-level node.

  6. If a Scenario node has children, validateNode() recursively validates each child and labels children as {ScenarioLabel} > {ChildType} {Number}.

  7. If the canvas has more than one node and no edges, it adds Node is not connected to any other node for each node.

  8. 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

canvasMetadata exists

Quest has no content

Canvas

canvasMetadata.nodes is non-empty

Quest has no content nodes

Unknown

Node data and type exist

Node is missing data or type

Text

textContent has non-HTML text after tag stripping

Text content is empty

Image

imageContent is non-empty

Image URL is missing

Video

videoContent is non-empty

Video URL is missing

Code

codeContent is non-empty

Code content is empty

Discussion (Internal)

title is non-empty

Discussion title is empty

Discussion (External)

externalLink is non-empty

Dicussion link is missing

Discussion

discussionPrompt is non-empty

Discussion prompt is empty

File

fileContent is non-empty

File URL is missing

Link

linkContent is non-empty

Link URL is missing

Audio

musicContent is non-empty

Audio URL is missing

Reflection

reflectionInstructions is non-empty

Reflection Instructions is empty

Question

questionText is non-empty

Question text is empty

Question

choices exists and has at least one item

Question has no choices

Question

Every choice has text

One or more choices are empty

Quiz (Hub source)

activityId is non-empty

Quiz has no Question Hub activity selected

Quiz (Custom source)

questions exists and has at least one item

Quiz has no questions

Quiz question

text is non-empty

Question {index} text is empty

Quiz question

Every choice with a choices array has text

Question {index} has empty choices

Scenario

children exists and has at least one item

Scenario has no content

Multi-node canvas

Edges exist when there is more than one top-level node

Node is not connected to any other node

Creator Submit / Publish Workflow

Implemented in hooks/quest-editor/useEditorActions.ts.

  1. User changes quest status to submitted or published.

  2. The hook saves the canvas through saveCanvasNow() first.

  3. The hook posts { quest_id } to /api/creator/validate-quest-content.

  4. If validation fails, toast.error(validateData.message || "Cannot Submit: Quest has empty nodes.") is shown and the status change stops.

  5. If validation passes and the new status is published, the hook creates forum posts for Discussion nodes.

  6. The hook then calls /api/creator/update-quest to apply the status change.

Reviewer Approval Workflow

Implemented in app/api/reviewer/update-quest-status/route.ts.

  1. Request body is validated by updateQuestStatusSchema.

  2. The route fetches creator_id, title, and canvas_metadata.

  3. If publishing_status is approved, it calls validateQuestContent().

  4. If validation errors exist, it returns a bad request and does not update the quest status.

  5. If validation passes, it updates quests.publishing_status and 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.

  1. Learner submits a quiz.

  2. In non-preview and non-SCORM mode, QuizRenderer posts displayed questions and answers to /api/learner/validate-quiz-submission.

  3. The API validates payload shape, then calls validateQuizSubmission().

  4. Missing answers become field-level errors.

  5. Essay answers enforce minWords and maxWords when configured.

  6. Image-map answers must be an object with numeric x and y.

  7. The renderer maps returned errors by questionId, highlights invalid cards, and scrolls to the first invalid question.

  8. If validation passes, grading proceeds through gradeQuizQuestion().


TESTING & QUALITY ASSURANCE

Dependencies

Dependency

Usage

Supabase quests table

Reads canvas_metadata for quest validation.

Auth helpers

authenticateUser() protects creator validation; reviewer route also uses role checks.

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 500 responses 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:

  1. Deploy validation library and API route changes together.

  2. Verify creator submit and publish flows against quests with valid and invalid nodes.

  3. Verify reviewer approval rejects incomplete quests.

  4. Verify learner quiz submissions reject missing answers in standard learner mode.

  5. 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-content success, missing quest_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 QuizRenderer error 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 skip data.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 Cannot submit - ...

validateQuestContent() found incomplete canvas nodes.

lib/validation/quest-validation.ts; inspect quests.canvas_metadata

Creator fixes a node but still sees the old error

Canvas save failed or status flow validated stale metadata.

hooks/quest-editor/useEditorActions.ts; saveCanvasNow() behavior.

Reviewer cannot approve a quest

Approval path runs the same quest validator and found invalid content.

app/api/reviewer/update-quest-status/route.ts

All nodes show disconnected errors

Quest has more than one top-level node and no saved edges.

app/api/reviewer/update-quest-status/route.ts

Scenario child errors are hard to locate

Errors are labeled with parent and child type counters, not the child display label.

Scenario children payload in canvas_metadata

Learner quiz submission highlights questions

/api/learner/validate-quiz-submission returned QuizSubmissionError[].

components/learner/renderers/QuizRenderer.tsx

Learner quiz validates in production but not preview

Preview intentionally skips the validation endpoint.

QuizRenderer if (!isPreview && !isSCORM) branch.

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.


Was this article helpful?