Skip to content

feat: implement patch#40

Merged
mehdiasadli merged 1 commit into
mainfrom
v26-05-05
May 5, 2026
Merged

feat: implement patch#40
mehdiasadli merged 1 commit into
mainfrom
v26-05-05

Conversation

@mehdiasadli
Copy link
Copy Markdown
Contributor

@mehdiasadli mehdiasadli commented May 5, 2026

Summary by CodeRabbit

Release Notes — v26.05.05

  • New Features

    • Badge award detail pages with shareable previews and earned-date context
    • See who reacted on posts and comments with a scrollable reactor list
    • Export packs and topics in JSON, YAML, XML, CSV, or TXT formats
    • Elo rating trend chart showing ranked game history
    • Enhanced badge filtering by rarity, earned status, and sort options
    • Draft packs section on user profiles
    • Admin user role management interface
  • Improvements

    • Refined Elo calculation system with accuracy and provisional-player factors
    • Better difficulty rating for skipped questions
    • Improved stats cards with delta indicators and formatted ratios

@vercel
Copy link
Copy Markdown

vercel Bot commented May 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
xamsa-web Ready Ready Preview, Comment May 5, 2026 7:17am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

This release (v26.05.05) introduces badge award detail pages with OG image sharing, dynamic badge rarity computation, pack/topic export in multiple formats, admin user role management, reaction drill-down UI, a refined Elo system with history tracking and a new pairwise calculation model, stat card refactoring, and a "non-click" difficulty-rate signal for unrevealed questions. Database schema gains per-player Elo snapshots; multiple new API endpoints and React components are added across the codebase.

Changes

Badge Award Details & Rarity System

Layer / File(s) Summary
Data Shape & Contracts
packages/schemas/src/modules/badge.ts, packages/api/src/og-data.ts
BadgeEarnerRowSchema adds badgeId; new schemas for GetBadgeCatalogStatsOutputSchema, FindBadgeAwardInputSchema/OutputSchema, and BadgeAwardOgPayload for catalog stats and award detail queries.
Rarity Computation
packages/utils/src/badges.ts
Remove static rarity field from Badge type and catalog entries; add computeBadgeRarity(uniqueEarners, totalEligibleUsers), BADGE_RARITY_THRESHOLDS, and BADGE_RARITY_RANK for runtime tier calculation.
Backend Services
packages/api/src/modules/badge/service.ts, packages/api/src/og-data.ts
Add getBadgeCatalogStats() to aggregate per-badge totals and eligible users; add findBadgeAward() to fetch single award by id; add getBadgeAwardOgData() for OG image data generation.
API Endpoints
packages/api/src/modules/badge/router.ts
Expose public getCatalogStats and findAward procedures in badgeRouter.
Routes & OG Images
apps/web/src/routes/badges/$badgeId/awards/$awardId.tsx, apps/web/src/routes/api/og/badges/$badgeId/awards/$awardId/og[.]png.tsx, apps/web/src/routeTree.gen.ts
Add new file routes for award detail page (with SEO metadata and share link) and OG image generation; update route tree with new paths.
OG Template
apps/web/src/lib/og/templates/badge-award.tsx
Create BadgeAwardOg component rendering earned badge metadata with formatted dates and footer links.
Directory UI
apps/web/src/routes/badges/index.tsx
Refactor badges page with query-state filters/sort (rarity, earned status, name/date), catalog stats queries, dynamic rarity display, and share percentage on cards; add FilterChip component.
Profile Badge Section
apps/web/src/components/profile-badges-section.tsx
Update to show recently earned badges, truncatable per-category lists, and links to award detail routes; add TabPill component for tab controls.
Earners List
apps/web/src/routes/badges/$badgeId/index.tsx
Add query-state driven filters (username, gameCode, earnedAt range); update row navigation to link to award detail route; change empty state messaging based on active filters.

Pack & Topic Export

Layer / File(s) Summary
Data Contracts & Serialization
packages/schemas/src/modules/pack.ts, packages/schemas/src/modules/topic.ts, packages/api/src/modules/topic/structured-export.ts
Define ExportFormat (json/yaml/xml/csv/txt), input/output schemas for pack and topic export; implement serializeTopics dispatcher and per-format serializers (JSON stringification, YAML, XML via XMLBuilder, CSV/TXT semicolon-delimited blocks).
Backend Services
packages/api/src/modules/pack/export.ts, packages/api/src/modules/topic/export.ts
Add exportPack(slug, format, userId) and exportTopic(packSlug, topicSlug, format, userId) with authorization (author/staff only) and serialized output generation.
API Endpoints
packages/api/src/modules/pack/router.ts, packages/api/src/modules/topic/router.ts
Expose export procedures in packRouter and topicRouter using verifiedProcedure.
Export Menu Components
apps/web/src/components/export-menu.tsx
Create PackExportMenu and TopicExportMenu with format selector, download trigger, loading/error toasts, and browser-side blob download helper.
UI Integration
apps/web/src/routes/packs/$packSlug/edit/index.tsx, apps/web/src/routes/packs/$packSlug/topics/$topicSlug/edit/index.tsx, apps/web/src/routes/packs/$packSlug/topics/$topicSlug/index.tsx, apps/web/src/components/pack-actions-menu.tsx
Wire PackExportMenu and TopicExportMenu into edit headers and topic toolbar; update pack-actions-menu to include export submenu with format options.

Admin User Role Management

Layer / File(s) Summary
Data Contracts
packages/schemas/src/modules/admin.ts
Add UpdateUserRoleInputSchema (userId, role) and UpdateUserRoleOutputSchema (userId, role, sessionsRevoked).
Backend Service
packages/api/src/modules/admin/update-user-role.ts
Implement updateUserRole() with validation (no self-change, no admin-role changes), transaction-based role update, and atomic session revocation.
API Endpoint
packages/api/src/modules/admin/router.ts
Expose updateUserRole procedure in adminRouter using adminProcedure.
Role Cell Component
apps/web/src/components/admin/user-role-cell.tsx
Create UserRoleCell with inline role selector (disabled for non-admins, self, or current-admin targets), confirmation dialog, React Query mutation, and success/error toasts.
Dashboard Integration
apps/web/src/routes/dashboard/users/index.tsx
Update users table "Role" column to use UserRoleCell component with userId/username/role props.

Reaction Breakdown UI

Layer / File(s) Summary
Data Contracts
packages/schemas/src/modules/reaction.ts
Add ListReactorsInputSchema (postId/commentId mutual validation, optional type), ReactorRowSchema (id, type, createdAt, user profile), and ListReactorsOutputSchema (cursor-paginated output).
Backend Service
packages/api/src/modules/reaction/service.ts
Implement listReactors() with Prisma filtering, cursor pagination, and reactor user field selection.
API Endpoint
packages/api/src/modules/reaction/router.ts
Expose listReactors as publicProcedure in reactionRouter.
Reaction Bar UI
apps/web/src/components/reactions/reaction-bar.tsx
Refactor ReactionBreakdownDialog with stateful view switching (summary ↔ reactors); add ReactorList component with infinite-query pagination, loading spinner, empty state, and "Load more" button; update dialog headers and back navigation.

ELO System & Stats UI

Layer / File(s) Summary
Database Schema
packages/db/prisma/schema/game.prisma, packages/db/prisma/migrations/20260505092600_player_elo_history/migration.sql, packages/schemas/src/db/schemas/models/Player.schema.ts, packages/schemas/src/db/schemas/enums/PlayerScalarFieldEnum.schema.ts
Add three nullable integer Elo snapshot fields to Player: eloRatingBefore, eloRatingAfter, eloDelta.
Game Finalization
packages/api/src/modules/game/finalize-game.ts
Store per-player Elo snapshots (eloRatingBefore, eloRatingAfter, eloDelta) during ranked game finalization; include gamesPlayedBefore, correctAnswers, incorrectAnswers, expiredAnswers in Elo delta calculation input.
ELO Calculation Engine
packages/utils/src/elo.ts, packages/utils/src/elo.test.ts
Replace average-opponent model with pairwise multiplayer comparison; add gamesPlayedBefore provisional-player scaling via effective K-factor; add accuracy modifier from correct-answer ratio; add test coverage for multi-player scenarios, tied scores, and provisional effects.
Elo History Data
packages/api/src/modules/user/service.ts, packages/api/src/modules/user/router.ts, packages/schemas/src/modules/user.ts
Implement getEloHistory() service and public endpoint; add GetEloHistoryInputSchema/OutputSchema; include gamesPlayedBefore, correctAnswers/Incorrect/Expired in returned history rows and stats.
Derived Stats
packages/schemas/src/modules/user.ts, packages/api/src/modules/user/service.ts
Extend GetMyStatsOutputSchema with derived block containing pre-computed ratio fields (correct-answer rate, win rate, etc.) guarded against division-by-zero.
Chart Component
apps/web/src/components/stats/elo-trend-chart.tsx
Create EloTrendChart to render recharts line plot from EloHistoryRow[] with X-axis dates, Y-axis Elo values, reference line at start Elo, and custom tooltip showing delta and rank.
Stat Card Components
apps/web/src/components/stats/stat-card.tsx, apps/web/src/components/stats/stats-grid.tsx
Create StatCard (icon, label, value, optional delta/hint) and StatsGrid/StatsGroup layout wrappers; add formatRatio() and formatDelta() utilities for display formatting.
Profile Stats Section
apps/web/src/routes/u/$username.tsx
Refactor "Stats" tab to load Elo history, render EloTrendChart, and use new StatsGroup/StatCard components with derived stat values; add "Drafts & in-progress" section in Packs tab with owner-only draft pack list.
Home Page Stats
apps/web/src/routes/index.tsx
Replace StatTile grid with StatsGrid layout; update stat cards to use StatCard component with derived metrics and formatRatio() formatting for win/correct/podium rates.

Difficulty Rate Non-Click Signal

Layer / File(s) Summary
Specification
DIFFICULTY_RATE_IMPLEMENTATION_SPEC.md
Document v26.05.05 feature: when question flips to revealed state, apply skip-based QDR nudge (K_SKIP = K0/4) to active, high-Elo (≥1000) non-clickers to increase question difficulty; skip does not increment qdrScoredAttempts; forward-only with no backfill.
QDR Utilities
packages/utils/src/difficulty-rate.ts, packages/utils/src/difficulty-rate.test.ts
Add K_SKIP and MIN_ELO_FOR_SKIP_SIGNAL constants; implement computeQdrSkipUpdate() to apply reduced K-factor update treating skip as outcome 0, returning updated qdrEloEquiv/qdr without incrementing scored attempts; add test coverage for skip behavior at threshold boundaries.
Click Resolution Integration
packages/api/src/modules/click/service.ts
In resolveClick(), when willFlipReveal && !isArchived, select active, high-Elo non-clickers and apply computeQdrSkipUpdate() for each, then persist updated question QDR/TDR/PDR if changed.

Release Management

Layer / File(s) Summary
App Release Manifest
packages/utils/src/app-releases.ts
Update current version to patch 5; add new v26.05.05 release entry with updated titles and highlights (badge details, Elo/xDR improvements, reaction drill-down).
Roadmap Content
apps/web/src/lib/roadmap-content.ts
Set v26.05.05 implemented: true; add roadmap items ("Badge detail share previews", "Better ELO system", "Better xDR system", "See who reacted"); remove "Badge detail share previews" from v26.05.06 to avoid duplication.

Sequence Diagram(s)

sequenceDiagram
    participant User as User (Browser)
    participant Web as Web App
    participant API as API Backend
    participant DB as Database
    
    User->>Web: View badge award detail<br/>(/badges/$badgeId/awards/$awardId)
    activate Web
    Web->>API: orpc.badge.findAward($awardId)
    activate API
    API->>DB: SELECT award + badge + user + game + topic
    DB-->>API: award data
    API-->>Web: award metadata
    deactivate API
    Web->>Web: Generate SEO metadata +<br/>OG image URL
    Web-->>User: Render award page<br/>+ share button
    deactivate Web
    
    User->>User: Click "Copy share link"
    User->>Web: Copy canonical URL
    Web-->>User: Success toast
    
    User->>User: Share on social media<br/>(OG image preview)
    User->>Web: GET /api/og/badges/$badgeId/awards/$awardId/og.png
    activate Web
    Web->>API: getBadgeAwardOgData($awardId)
    activate API
    API->>DB: SELECT award + badge + user
    DB-->>API: award + badge data
    API-->>Web: OG data payload
    deactivate API
    Web->>Web: Render BadgeAwardOg template
    Web-->>User: OG PNG image
    deactivate Web
Loading
sequenceDiagram
    participant User as User (Browser)
    participant Web as Web App
    participant API as API Backend
    participant DB as Database
    
    User->>Web: View badges directory
    activate Web
    Web->>API: orpc.badge.getCatalogStats()
    activate API
    API->>DB: SELECT COUNT(DISTINCT player_badge_award.id)<br/>+ COUNT(DISTINCT user_id) per badge<br/>+ COUNT(DISTINCT user_id) total
    DB-->>API: catalog stats
    API-->>Web: { rows: [...], totalEligibleUsers }
    deactivate API
    
    Web->>Web: For each badge:<br/>rarity = computeBadgeRarity(<br/>uniqueEarners,<br/>totalEligibleUsers)
    
    User->>Web: Filter by rarity / earned status /<br/>sort by rarity/name/earns
    Web->>Web: Filter + sort badges using<br/>catalog stats + rarity ranks
    Web-->>User: Render directory with<br/>rarity tiers + stats
    deactivate Web
    
    User->>Web: Click badge → View earners
    activate Web
    Web->>API: orpc.badge.listEarners()<br/>with filters (username, dateRange, etc.)
    activate API
    API->>DB: SELECT * FROM player_badge_award<br/>WHERE badges match filters
    DB-->>API: earned rows
    API-->>Web: paginated earners
    deactivate API
    Web-->>User: Render earners list<br/>+ link to award detail
    deactivate Web
Loading
sequenceDiagram
    participant User as User (Browser)
    participant Web as Web App
    participant API as API Backend
    participant DB as Database
    
    User->>Web: View profile stats tab
    activate Web
    Web->>API: orpc.user.getEloHistory()<br/>{ username, limit: 30 }
    activate API
    API->>DB: SELECT game, rank, elo snapshot fields<br/>FROM player WHERE user matches<br/>ORDER BY game.finishedAt DESC
    DB-->>API: ordered Elo history rows
    API-->>Web: { items: [...] }
    deactivate API
    
    Web->>Web: Reverse history (oldest→newest)<br/>Map to chart data
    Web->>Web: computeEloTrendChart(history)
    Web-->>User: Render EloTrendChart<br/>+ historical deltas
    
    Web->>API: orpc.user.getMyStats()
    activate API
    API->>DB: SELECT user stats
    DB-->>API: user totals<br/>+ computed derived ratios
    API-->>Web: { ...stats, derived: {...ratios...} }
    deactivate API
    
    Web->>Web: Render StatsGrid<br/>with StatCard per metric<br/>using derived values + formatRatio()
    Web-->>User: Stats dashboard<br/>with Elo trend + stat cards
    deactivate Web
Loading
sequenceDiagram
    participant User as User (Browser)
    participant Web as Web App
    participant API as API Backend
    
    User->>Web: Open pack/topic edit page
    activate Web
    Web-->>User: Render page with<br/>export menu button
    deactivate Web
    
    User->>Web: Click "Export pack"<br/>→ Select format (JSON/YAML/XML/CSV/TXT)
    activate Web
    Web->>API: orpc.pack.export()<br/>{ slug, format }
    activate API
    API->>API: exportPack():<br/>fetch pack + topics + questions,<br/>authorize (author/staff),<br/>serialize to format
    API-->>Web: { filename, mimeType, body }
    deactivate API
    
    Web->>Web: createObjectURL(blob)<br/>Trigger browser download
    Web->>Web: Revoke object URL
    Web-->>User: File downloaded
    Web-->>User: Success toast
    deactivate Web
Loading
sequenceDiagram
    participant User as User (Browser)
    participant Web as Web App
    participant API as API Backend
    participant DB as Database
    
    User->>Web: View post/comment reactions
    activate Web
    Web->>Web: Show reaction counts
    Web-->>User: Reaction summary (emoji + counts)
    deactivate Web
    
    User->>Web: Click reaction type to drill down
    activate Web
    Web->>Web: Switch dialog view → "reactors"
    Web->>API: orpc.reaction.listReactors()<br/>{ postId, type, limit, cursor }
    activate API
    API->>DB: SELECT id, type, createdAt, user fields<br/>FROM reaction WHERE postId + type<br/>ORDER BY createdAt DESC<br/>LIMIT + cursor pagination
    DB-->>API: reactor rows
    API-->>Web: { items, nextCursor }
    deactivate API
    
    Web->>Web: Render ReactorList<br/>with avatars + reaction emoji
    Web-->>User: Drilled-down reactor list
    
    User->>Web: Click "Load more"
    Web->>API: orpc.reaction.listReactors()<br/>{ postId, type, cursor: nextCursor }
    activate API
    API->>DB: Fetch next page
    DB-->>API: more reactor rows
    API-->>Web: next items + cursor
    deactivate API
    Web->>Web: Append to list
    Web-->>User: More reactors loaded
    deactivate Web
Loading
sequenceDiagram
    participant Game as Game Finalization
    participant Elo as ELO Calculator
    participant DB as Database
    
    Game->>Elo: calculateEloDeltas(players[], options)
    activate Elo
    
    loop For each player P
        loop Against each other player O
            Elo->>Elo: expectedCorrect(P.rating, O.rating)
            Elo->>Elo: actualOutcome = <br/>blended(P.rank, P.score,<br/>O.rank, O.score)
            Elo->>Elo: delta_contribution +=<br/>(actualOutcome - expectedCorrect)<br/>× K_factor(P.gamesPlayedBefore)
        end
        Elo->>Elo: Apply accuracy modifier<br/>from correctAnswers/total
        Elo->>Elo: Clamp delta to [-K, K]
        Elo->>Elo: Map P.id → rounded delta
    end
    
    Elo-->>Game: Map<playerId, delta>
    deactivate Elo
    
    Game->>DB: For each player:<br/>eloRatingAfter = eloRatingBefore + delta<br/>Store snapshot (before, after, delta)
    activate DB
    DB-->>Game: Updated
    deactivate DB
Loading

Estimated Code Review Effort

🎯 5 (Critical) | ⏱️ ~120 minutes

This is a substantial multi-feature release spanning 7 distinct cohorts with significant logic density (ELO calculation model overhaul, export serialization across formats, dynamic rarity computation), database schema changes, API endpoint proliferation (~8 new endpoints), and UI refactoring across multiple pages. The variety of domain-specific reasoning required (game logic, export formats, admin authorization, OG image generation, dynamic rarity ranking) and the breadth of files affected (40+ modified, 20+ new) necessitate careful line-by-line review to ensure correctness of calculations, authorization checks, data consistency, and UI state management.

Possibly Related PRs

  • asmelabs/xamsa#36: Extends the reaction module with a new listReactors service and router entry used for the drill-down reactor list UI in this PR.
  • asmelabs/xamsa#13: Modifies the same finalizeGame Elo snapshot/rollup logic that this PR updates with per-player Elo field persistence.
  • asmelabs/xamsa#4: Related Elo calculation improvements; both PRs refactor the core Elo delta computation model in packages/utils/src/elo.ts.

Suggested Labels

app:web, pkg:api, database, size:xl, feature


🐰 A badge for your deeds so bright,
Share the joy on social night,
Export packs in formats five,
Elo trends keep ranks alive,
Reactions drill down with glee—
v26.05.05 sets rankings free!

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch v26-05-05

@mehdiasadli mehdiasadli merged commit d353e52 into main May 5, 2026
4 of 5 checks passed
@mehdiasadli mehdiasadli deleted the v26-05-05 branch May 5, 2026 07:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant