Skip to content

feat(profile): add barebones achievements (@jonasiwnl)#7865

Open
jonasiwnl wants to merge 3 commits intomonkeytypegame:masterfrom
jonasiwnl:feat/achievements
Open

feat(profile): add barebones achievements (@jonasiwnl)#7865
jonasiwnl wants to merge 3 commits intomonkeytypegame:masterfrom
jonasiwnl:feat/achievements

Conversation

@jonasiwnl
Copy link
Copy Markdown

Summary

  • add a frontend-only achievements derivation utility based on existing profile data
  • add a profile achievements section for account and public profile pages
  • add targeted tests for milestone derivation behavior

Testing

  • pnpm exec vitest run tests/utils/achievements.spec.ts
  • pnpm exec oxlint src/ts/utils/achievements.ts src/ts/components/pages/profile/Achievements.tsx src/ts/components/pages/profile/UserProfile.tsx tests/utils/achievements.spec.ts --type-aware --type-check

Notes

  • implements a barebones interpretation of Achievements #64 without backend persistence or schema changes

Copilot AI review requested due to automatic review settings April 25, 2026 03:40
@monkeytypegeorge monkeytypegeorge added the frontend User interface or web stuff label Apr 25, 2026
@github-actions github-actions Bot added the waiting for review Pull requests that require a review before continuing label Apr 25, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a frontend-only “Achievements” section to profile pages by deriving milestone progress from existing UserProfile data, plus targeted unit tests for the derivation logic.

Changes:

  • Add getAchievements() utility + achievement definitions (tests, streak, typing time, level, PB WPM).
  • Render an Achievements grid on profile pages via UserProfile.
  • Add Vitest coverage for unlock behavior, percent clamping, and PB/level derivation.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
frontend/src/ts/utils/achievements.ts New achievement derivation utility + milestone definitions.
frontend/src/ts/components/pages/profile/Achievements.tsx New UI section to display achievement cards + progress bars.
frontend/src/ts/components/pages/profile/UserProfile.tsx Mount Achievements section in the profile page layout.
frontend/tests/utils/achievements.spec.ts New Vitest tests covering derivation/unlock logic.

lbOptOut: false,
streak: 0,
maxStreak: 0,
details: {},
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

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

getProfile() sets details: {} but UserProfileDetails requires at least bio and keyboard keys (even if empty). With type-aware lint/typecheck this should fail. Either omit details entirely (since optional on UserProfile) or provide { bio: "", keyboard: "" } (plus any other required keys).

Suggested change
details: {},
details: {
bio: "",
keyboard: "",
},

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +28
const achievements = () => getAchievements(props.profile);

return (
<div class="grid gap-4 rounded bg-sub-alt p-4">
<div class="flex items-center justify-between gap-4">
<div>
<div class="text-lg text-text">Achievements</div>
<div class="text-sm text-sub">
A barebones read-only overview of profile milestones.
</div>
</div>
<div class="text-sm text-sub">
{achievements().filter((it) => it.unlocked).length}/
{achievements().length} unlocked
</div>
</div>
<div class="grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-3">
<For each={achievements()}>
{(achievement) => <AchievementCard achievement={achievement} />}
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

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

achievements() recomputes a new array on every call, and it's called multiple times in the same render (filter/length + list). In Solid this can cause unnecessary work and churn (new array identity for <For>). Use createMemo(() => getAchievements(props.profile)) (and derive unlocked count/total from that memo) so it only recalcs when props.profile changes.

Copilot uses AI. Check for mistakes.
Comment on lines +109 to +118
const rawProgress = definition.getProgress(profile);
const progress = Math.min(rawProgress, definition.target);
const unlocked = rawProgress >= definition.target;

return {
...definition,
progress: rawProgress,
progressPercent:
definition.target === 0 ? 100 : (progress / definition.target) * 100,
unlocked,
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

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

Achievement.progress is set to rawProgress, but progressPercent is computed from a clamped progress value. This makes the API internally inconsistent (a consumer might assume progress matches the percentage/target). Consider returning progress: Math.min(rawProgress, target) and (if needed) a separate rawProgress field for labels/tooltips.

Copilot uses AI. Check for mistakes.
@fehmer
Copy link
Copy Markdown
Member

fehmer commented Apr 26, 2026

Hi @jonasiwnl, thanks for your contribution.

This feature already looks very cool. Few suggestions to discuss.

Ui

Currently it takes up a lot of space. Especially if we add more archivements in the future.
Maybe we can compact it into just the number of archivements reached like 6/12and just the icon of a few completed archivements (hardest once first). Show the name and subtitle on mouseover.
Either have it as a box like leaderboard summary or somehow integrate it into the user card on the top.

We could have a way to either expand the section to show the full summary (like we do for advanced filters on the account page) or open a modal (like we do with show all personal bests).

Archivements

  • time typed: 1, 10, 25, 50, 75, 100, 250, 500, 750, 1000 hours
  • WPM: 40 (faster than global average), 70 (faster than the mt average)
  • languages: check for languages in pb, exclude code languages and have some milestones (maybe 2, 5, 10?).
  • Track for code languages separate
  • have pb in all modes time 15, 30, 60, 120 and words 10,25,50,100, one custom and one zen
  • 4 archivements based on restart rate (very low, low, high and very high) maybe 1%, 10%, 90% and 99%?

@Miodec what do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

frontend User interface or web stuff waiting for review Pull requests that require a review before continuing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants