From dd86f928f005f62c04e1468011d3d2662434eaeb Mon Sep 17 00:00:00 2001 From: river0525 <0525sotaro@gmail.com> Date: Wed, 15 Apr 2026 10:09:36 +0900 Subject: [PATCH 01/14] feat: show relative evaluation badge on confirmed grade icons When a task has a confirmed grade and sufficient votes to compute a median, display a badge (+, ++, -, --) in the top-right corner of the grade icon to indicate how the community perceives the difficulty relative to the official grade. - Add calcGradeDiff / getRelativeEvaluationLabel utilities with tests - Render amber badge for harder (+/++) and sky badge for easier (-/--) - No badge shown for PENDING tasks or when vote count is below threshold Co-Authored-By: Claude Sonnet 4.6 --- .../votes/components/VotableGrade.svelte | 29 ++++++++- .../votes/utils/relative_evaluation.test.ts | 64 +++++++++++++++++++ .../votes/utils/relative_evaluation.ts | 44 +++++++++++++ 3 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 src/features/votes/utils/relative_evaluation.test.ts create mode 100644 src/features/votes/utils/relative_evaluation.ts diff --git a/src/features/votes/components/VotableGrade.svelte b/src/features/votes/components/VotableGrade.svelte index 623692cd9..ee28df47e 100644 --- a/src/features/votes/components/VotableGrade.svelte +++ b/src/features/votes/components/VotableGrade.svelte @@ -17,6 +17,10 @@ import { getTaskGradeLabel } from '$lib/utils/task'; import { nonPendingGrades, resolveDisplayGrade } from '$features/votes/utils/grade_options'; + import { + calcGradeDiff, + getRelativeEvaluationLabel, + } from '$features/votes/utils/relative_evaluation'; import { SIGNUP_PAGE, LOGIN_PAGE, EDIT_PROFILE_PAGE } from '$lib/constants/navbar-links'; import GradeLabel from '$lib/components/GradeLabel.svelte'; @@ -52,6 +56,14 @@ taskResult.grade === TaskGrade.PENDING && displayGrade !== TaskGrade.PENDING, ); + // Relative evaluation badge: shown only when grade is confirmed and a median vote exists. + const relativeEvaluationLabel = $derived.by(() => { + if (taskResult.grade === TaskGrade.PENDING || !estimatedGrade) { + return ''; + } + return getRelativeEvaluationLabel(calcGradeDiff(taskResult.grade, estimatedGrade)); + }); + let isOpening = $state(false); let votedGrade = $state(null); let voteAbortController: AbortController | null = null; @@ -156,11 +168,26 @@ onclick={() => onTriggerClick()} > - Voted grade: {getTaskGradeLabel(displayGrade)}{isProvisional ? ', provisional' : ''} + Voted grade: {getTaskGradeLabel(displayGrade)}{relativeEvaluationLabel + ? `, relative evaluation: ${relativeEvaluationLabel}` + : ''}{isProvisional ? ', provisional' : ''} + {#if relativeEvaluationLabel} + {@const isHarder = relativeEvaluationLabel.startsWith('+')} + + {/if} +