Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
064c2c8
ref: added aria label to edit-question button
Marty-Byrde Jan 28, 2026
aea7ce6
ref: added translations to role in `KnowCheckCard`
Marty-Byrde Jan 28, 2026
7e14fb7
ref: moved `Field` description into `Tooltip`
Marty-Byrde Jan 28, 2026
8c4897c
style: updated `color-scheme` to change with color-mode
Marty-Byrde Jan 28, 2026
bc6ef0d
style: improved `Input` disabled color intensity
Marty-Byrde Jan 28, 2026
96df01d
style: prefixed hover, focus styles with enabled in `Button`
Marty-Byrde Jan 28, 2026
81a8979
style: updated outline `Button` variant ring styles
Marty-Byrde Jan 28, 2026
b2540a1
feat: introduce `DegradingScoreIndicator` component
Marty-Byrde Jan 28, 2026
9018bdc
ref: positioned `DegSIndicator` top left
Marty-Byrde Jan 28, 2026
09819e1
ref: positioned `DegSIndicator` in footer
Marty-Byrde Jan 28, 2026
122a322
style: improved `DegrScoreIndic` color range
Marty-Byrde Jan 29, 2026
5d1ef09
ref: updated retainment level based tooltip contents
Marty-Byrde Jan 29, 2026
a7fd286
ref: re-introduced `DegrSIndicator` to card header
Marty-Byrde Jan 29, 2026
0bf1d38
style: added light mode colors to `DegSIndicator`
Marty-Byrde Jan 29, 2026
c11a095
ref: fixed typos in color and text declarations
Marty-Byrde Jan 29, 2026
da282a6
feat: drafted `computeKnowledgeRetainment` module
Marty-Byrde Jan 29, 2026
43abd96
Merge branch 'ref/minor-code-and-styling-improvements' into feat/know…
Marty-Byrde Jan 31, 2026
2a95d4c
Merge branch 'canary' into feat/knowledgeCheck-degrading-score
Marty-Byrde Feb 2, 2026
c749d09
ref: moved user role into top center of `KnowCCard`
Marty-Byrde Feb 2, 2026
d674f9d
style: aligned `DegScoreIndicator` with card header
Marty-Byrde Feb 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions src/components/checks/DegradingScoreIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import Tooltip from '@/src/components/Shared/Tooltip'
import { cn } from '@/src/lib/Shared/utils'

/**
*
* @param knowledgeCheckScore A number between 0 and 100 where 100 means the user has likely retained 100% of the knowledge that he learned and had his exam about.
*/
export default function DegradingScoreIndicator({ knowledgeCheckScore, className }: { knowledgeCheckScore: number | undefined; className?: string }) {
// very well retained, well-retained, good overview, mediocre, basic, lost
let level: '95-100' | '80-95' | '60-80' | '40-60' | '20-40' | '0-20' | 'none' = 'none'

if (knowledgeCheckScore === undefined) level = 'none'
else if (knowledgeCheckScore >= 95) level = '95-100'
else if (knowledgeCheckScore >= 80) level = '80-95'
else if (knowledgeCheckScore >= 60) level = '60-80'
else if (knowledgeCheckScore >= 40) level = '40-60'
else if (knowledgeCheckScore >= 20) level = '20-40'
else if (knowledgeCheckScore >= 0) level = '0-20'

if (knowledgeCheckScore && knowledgeCheckScore > 100) knowledgeCheckScore = 100

let scoreMessage: string = ''

switch (level) {
case '95-100': {
scoreMessage = 'Almost 100% of the Knowledge is most likely still retained'
break
}
case '80-95': {
scoreMessage = 'Most of the Knowledge gained by this check is retained'
break
}
case '60-80': {
scoreMessage = 'Big Chunks of Knowledge are still retained'
break
}
case '40-60': {
scoreMessage = 'While some Knowledge parts are retained, a large part might have been forgotten'
break
}
case '20-40': {
scoreMessage = 'Most of the Knowledge delivered by this check is no longer retained'
break
}
case '0-20': {
scoreMessage = 'The contents and knowledge delivered by this check are no longer retained'
break
Comment on lines +24 to +47

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Localize user-facing score messages.

These strings bypass i18n, while the rest of the card uses localized labels. Please wire them through useScopedI18n (or equivalent) to keep translations consistent.

🤖 Prompt for AI Agents
In `@src/components/checks/DegradingScoreIndicator.tsx` around lines 24 - 47, The
switch in DegradingScoreIndicator sets user-facing strings directly to
scoreMessage (cases '95-100'...'0-20'), bypassing i18n; update it to use the
component's useScopedI18n (or the project's equivalent) to return localized
labels instead of hardcoded English strings, i.e., import/use the hook in
DegradingScoreIndicator, replace each literal assigned to scoreMessage with a
call to the i18n lookup key (e.g., t('degradingScore.almost100') or similar
scoped keys), and ensure keys exist in the locale files so the UI uses
translated messages.

}
case 'none':
}
Comment thread
Marty-Byrde marked this conversation as resolved.

return (
<Tooltip content={scoreMessage} delay={100}>
<div
className={cn(
'relative bg-neutral-300 text-xs select-none dark:bg-neutral-500/80',

level === '0-20' && 'bg-[oklch(80%_0.25_30)] dark:bg-[oklch(40%_0.12_30)]',
level === '20-40' && 'bg-[oklch(88%_0.08_45)] dark:bg-[oklch(30%_0.09_30)]',
level === '40-60' && 'bg-[oklch(88%_0.08_110)] dark:bg-[oklch(32%_0.2_110)]',
level === '60-80' && 'bg-[oklch(85%_0.08_116)] dark:bg-[oklch(32%_0.2_116)]',
level === '80-95' && 'bg-[oklch(85%_0.10_155)] dark:bg-[oklch(35%_0.3_155)]',
level === '95-100' && 'bg-[oklch(85%_0.2_140)] dark:bg-[oklch(40%_0.37_155)]',
className,
)}>
<span className='flex items-center justify-center'>{knowledgeCheckScore ?? 0}</span>
</div>
</Tooltip>
)
}
10 changes: 9 additions & 1 deletion src/components/checks/KnowledgeCheckCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@ import React from 'react'
import { motion } from 'framer-motion'
import { CrownIcon, LockIcon, UserPenIcon } from 'lucide-react'
import KnowledgeCheckMenu from '@/src/components/checks/(hamburger-menu)/KnowledgeCheckMenu'
import DegradingScoreIndicator from '@/src/components/checks/DegradingScoreIndicator'
import { ShareKnowledgeCheckButton } from '@/src/components/checks/ShareKnowledgeCheckButton'
import Card from '@/src/components/Shared/Card'
import { InitialsIcon } from '@/src/components/Shared/InitialsIcon'
import { useCurrentLocale, useScopedI18n } from '@/src/i18n/client-localization'
import { useSession } from '@/src/lib/auth/client'
import { computeKnowledgeRetainment } from '@/src/lib/checks/computeKnowledgeRetainment'
import { cn } from '@/src/lib/Shared/utils'
import { KnowledgeCheck } from '@/src/schemas/KnowledgeCheck'

export function KnowledgeCheckCard(check: KnowledgeCheck) {
const t = useScopedI18n('Components.KnowledgeCheckCard')
const retainmentScore = computeKnowledgeRetainment({ difficulty: check.difficulty })

const { data } = useSession()
const userId = data?.user.id
const isCollaborator = userId ? check.collaborators.includes(userId) : false
Expand All @@ -31,7 +35,11 @@ export function KnowledgeCheckCard(check: KnowledgeCheck) {
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
className={cn('relative flex h-full flex-col justify-between gap-10')}>
<div className='absolute top-3 right-4 left-4 flex items-center justify-between'>
<div className='absolute top-0 left-0 z-10 opacity-65'>
<DegradingScoreIndicator className='min-w-10 rounded-tl-md rounded-br-md p-3 px-3 text-xs' knowledgeCheckScore={retainmentScore} />
</div>
<div className='-mt-2 -mb-9 flex items-center justify-between'>
<div className='w-14' />
<DisplayUserRole role={role} />
<div className='flex gap-1'>
<ShareKnowledgeCheckButton check={check} />
Expand Down
13 changes: 13 additions & 0 deletions src/lib/checks/computeKnowledgeRetainment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { KnowledgeCheck } from '@/src/schemas/KnowledgeCheck'

/**
* This function computes the knowledge retainment score of a given user for a given check based on the difficulty and last practice / examination and the respective score.
* The more time has passed and the more difficult a check is the faster knowledge degrades --> thus lower retainment score.
* @param
*/
export function computeKnowledgeRetainment({ difficulty }: Pick<KnowledgeCheck, 'difficulty'>) {
const randomScore = Math.random() * 100
const skewedScore = randomScore * 1.7

return skewedScore > 100 ? 100 : Math.floor(skewedScore)
Comment thread
Marty-Byrde marked this conversation as resolved.
}
Loading