Goal: Allow instructors to mark nodes/assessments as "graded", set points_possible, and allow graders to record points_awarded when grading submissions. Keep behavior backwards-compatible and add server + client validation to match DB constraints.
- [ ] DB: add columns `node_assessments.points_possible`, `node_assessments.is_graded`, `submission_grades.points_awarded` and appropriate constraints/migrations
- [ ] Types: update `types/map.ts` with new fields
- [ ] Server: accept and validate `points_awarded` in grading APIs and auto-graders
- [ ] UI (Node editor): allow instructors to toggle "is graded" and set `points_possible`
- [ ] UI (Grader): show `points_possible` on grade dialog and collect `points_awarded`
- [ ] UI (Student): display points awarded in submission view and progress pages
- [ ] Tests: add unit tests for server validation and auto-grader behavior
- [ ] Docs & migration notes
- [ ] Rollout: migration + feature flag + minor analytics updates- The DB should remain authoritative: points must be integers (>= 0). We'll enforce server-side validation and optional DB check constraints.
- Existing pass/fail workflow must continue to work. Adding points is an additive feature; if
is_gradedorpoints_possibleare not set, behavior remains unchanged. - Keep UI minimal and consistent with existing components.
Place a SQL migration under supabase/migrations/ (or your migrations folder). Example migration:
-- migration: 2025xxxxxx_add_points_to_assessments_and_grades.sql
BEGIN;
ALTER TABLE public.node_assessments
ADD COLUMN IF NOT EXISTS points_possible integer NULL,
ADD COLUMN IF NOT EXISTS is_graded boolean NOT NULL DEFAULT false;
ALTER TABLE public.submission_grades
ADD COLUMN IF NOT EXISTS points_awarded integer NULL;
-- Ensure non-negative points_awarded
ALTER TABLE public.submission_grades
ADD CONSTRAINT IF NOT EXISTS submission_grades_points_check
CHECK (points_awarded IS NULL OR points_awarded >= 0);
COMMIT;Notes:
- We purposely keep
points_possiblenullable so older nodes are unaffected.is_gradeddefaults to false for explicit intent. - If you want a hard DB constraint to ensure
points_awarded <= points_possible, you can add a trigger. That is more complex and can be added later.
Update types/map.ts:
-
NodeAssessment:
- add
points_possible?: number | null - (optional) use
is_graded?: booleanif you prefer optional vs required
- add
-
SubmissionGrade:
- add
points_awarded?: number | null
- add
Example diff (conceptual):
export interface NodeAssessment {
id: string;
node_id: string;
assessment_type: AssessmentType;
quiz_questions?: QuizQuestion[];
+ points_possible?: number | null;
+ is_graded?: boolean;
}
export interface SubmissionGrade {
id: string;
submission_id: string;
graded_by: string | null;
grade: Grade;
rating?: number;
+ points_awarded?: number | null;
comments?: string;
graded_at: string;
}Files to update (examples):
lib/supabase/grading.ts— extendgradeSubmission()to acceptpoints_awarded?: number | nulland validate itlib/supabase/assessment.ts— when auto-grading quizzes, setpoints_awardedequal to the computed correct count or a scaled value- API routes that accept grading JSON (if you have a
/api/gradingroute) — ensure they acceptpoints_awarded
Server validation rules:
- If
points_awardedprovided:- must be an integer (Number.isInteger)
- must be >= 0
- if
points_possibleis known (you may fetch it by assessment id), ensurepoints_awarded <= points_possible(optional but recommended)
- If
points_awardedis omitted, storeNULLin DB
API contract for grading endpoint (body JSON):
{
"submissionId": "uuid",
"grade": "pass|fail",
"comments": "string or null",
"rating": 1|null,
"points_awarded": 7|null,
"progressId": "uuid"
}Return shape: existing SubmissionGrade object with points_awarded populated.
Where to change
-
Node editor (instructor-facing): likely
components/map/NodeEditorPanel.tsxor similar- Add a toggle
is_graded(checkbox) - Add a number input
points_possible(integer >= 1) - Persist via existing
batchUpdateMap()or the node update endpoint
- Add a toggle
-
Grading dialog:
app/map/[id]/grading/grade-submission-form.tsx- If
node_assessments.points_possibleoris_gradedis present, render a number input forPoints awarded. - Validate it client-side: integer, 0 <= points_awarded <= points_possible
- Send
points_awardedalong with grade/rating/comments togradeSubmission()server call
- If
-
Submission display:
components/map/SubmissionItem.tsx(attachment)- Currently shows {grade.rating}/5. Add points display when
grade.points_awardedornode_assessments.points_possibleis present. - Example render:
Points: 7 / 10(or7 pointsif points_possible unknown)
- Currently shows {grade.rating}/5. Add points display when
Example UI behavior in grading form (small UX notes):
- Show
Points possible: 10as a read-only label if node defines it - Provide input for
Points awardeddefaulted to previous grade.points_awarded or blank - If
is_graded === falsebut instructor tries to award points, show a gentle warning and allow it (or disallow based on product decision)
lib/supabase/assessment.tsalready computes integer 1–5 rating for quizzes; extend logic to also computepoints_awardedas the number of correct answers (or scaled points)- Insert
points_awardedwhen creating auto-grade entries for quiz submissions
Suggested tests to add/update
- Unit tests for
lib/supabase/grading.ts:- Accept valid points_awarded within 0..points_possible
- Reject non-integer points (e.g., 3.5) and negative numbers
- Reject points greater than points_possible if you enforce that
- Integration tests for API route that handles grading
- UI tests (React Testing Library) for grade dialog to ensure validation prevents invalid input
- Add migration and types. Deploy server code that tolerates nullable
points_awarded(no UI yet). This is safe because it only adds nullable columns. - Deploy server validation and API acceptance for
points_awarded. - Release Node Editor UI so instructors can set
points_possible(behind feature flag if desired). - Release Grading UI changes to collect
points_awarded. - Monitor for errors and database constraint violations.
- Because
points_possibleandpoints_awardedare nullable andis_gradeddefaults false, no existing data is affected. - If you want to backfill
points_possiblefor existing nodes, create a follow-up migration with sensible defaults.
-
supabase/migrations/2025xxxx_add_points.sql— migration -
types/map.ts— type updates -
lib/supabase/grading.ts— signature and validation -
lib/supabase/assessment.ts— auto-grade points_awarded set -
app/map/[id]/grading/grade-submission-form.tsx— collect and validatepoints_awarded -
components/map/NodeEditorPanel.tsx— add UI foris_graded+points_possible -
components/map/SubmissionItem.tsx— display points (update UI shown in attachments) - tests/* — unit & integration tests
function validatePoints(points: number | null | undefined, pointsPossible?: number | null) {
if (points === null || points === undefined) return null;
const num = Number(points);
if (!Number.isInteger(num) || num < 0) throw new Error('points_awarded must be non-negative integer or null');
if (typeof pointsPossible === 'number' && pointsPossible !== null && num > pointsPossible) {
throw new Error('points_awarded cannot exceed points_possible');
}
return num;
}- Node Editor: "Graded assessment" toggle with helper text: "Enable grading for this node; instructors can set how many points this assessment is worth."
- Points input placeholder: "Points possible (integer)"
- Grader dialog: label
Points awarded (0 - {points_possible})and validation errorMust be an integer between 0 and {points_possible}.
If you'd like, I can proceed to implement these changes incrementally. Tell me which of the following you want next and I'll start:
- A) Create the SQL migration + update
types/map.ts+ server validation inlib/supabase/grading.ts(safe first step) - B) Implement the UI pieces (Node editor + grading form + display) after A is done
- C) Create PR-ready patches for all files (migration, types, server, UI) in one go
Which option should I start with?