Skip to content

feat: Implement Matchmaking Flow and Chess Variant Selection#676

Merged
gabito1451 merged 4 commits into
OpenKnight-Foundation:mainfrom
Dantama022:feature/chess-variant-selector
Apr 28, 2026
Merged

feat: Implement Matchmaking Flow and Chess Variant Selection#676
gabito1451 merged 4 commits into
OpenKnight-Foundation:mainfrom
Dantama022:feature/chess-variant-selector

Conversation

@Dantama022

@Dantama022 Dantama022 commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Closes #583


Implement Matchmaking Flow and Chess Variant Selection
This PR introduces a robust matchmaking system and a premium variant selection UI, while establishing a modern testing infrastructure for the frontend.

Key Features
Chess Variant Selection: Added a high-fidelity ChessVariantSelector component allowing users to choose between Standard, Rapid, Blitz, and Bullet formats.
Matchmaking Flow: Integrated the selection flow into the main dashboard, ensuring game parameters are correctly passed to the matchmaking context.
AI Personality Integration: Users are now prompted to select an AI personality before entering the matchmaking queue (via AiPersonalityModal).
Real-time Status: Implemented dynamic status overlays for searching, match found, and reconnection states with smooth animations.
Improved Accessibility: Conducted an accessibility audit and added ARIA roles/labels to core interactive elements.
Technical Improvements
Testing Infrastructure: Integrated Vitest and React Testing Library. Added configuration files (vitest.config.ts, vitest.setup.ts) and implemented unit tests for UI components.
Design System: Leveraged HSL-tailored gradients, glassmorphism, and Lottie animations to create a premium, state-of-the-art gaming interface.
Typed Constants: Centralized variant definitions in lib/chessVariants.ts for consistency across the application.
Verification Results
Unit Tests: Passed ChessVariantSelector.test.tsx ensuring correct state transitions and accessibility attributes.
Manual Flow: Verified the end-to-end flow from variant selection → personality selection → queue entry.

Summary by CodeRabbit

  • New Features
    • Choose from chess variants (Standard, Rapid, Blitz, Bullet) when setting up online matches
    • Variant details including average game time and descriptions now display throughout the matchmaking flow and game status panel
    • Queue status reflects your selected variant

@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cc6a96bf-eeba-4e46-af7e-477b59108cb3

📥 Commits

Reviewing files that changed from the base of the PR and between ff851b7 and 70ed7c1.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (1)
  • frontend/package.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/package.json

📝 Walkthrough

Walkthrough

This pull request introduces a chess variant selection feature to the frontend. It adds a new ChessVariantSelector component, defines chess variant data structures and utilities, integrates variant state into the matchmaking context, and updates existing pages and modals to display and handle variant selection throughout the matchmaking flow.

Changes

Cohort / File(s) Summary
Chess Variant Infrastructure
frontend/lib/chessVariants.ts
Introduces ChessVariant type, ChessVariantOption interface, CHESS_VARIANTS preset list with variant metadata (label, description, avg game time, UI classes), DEFAULT_CHESS_VARIANT, and getChessVariantById lookup function.
Matchmaking Context
frontend/context/matchmakingContext.tsx
Extends context to store chessVariant state alongside aiPersonality; adds setChessVariant setter; memoizes context value keyed by both fields.
Chess Variant Selector Component
frontend/components/ChessVariantSelector.tsx, frontend/components/__tests__/ChessVariantSelector.test.tsx
New client component rendering a responsive grid of variant selection buttons with styling and selection state; includes unit tests for rendering, selection states, and click interactions.
Page Integration
frontend/app/page.tsx
Updates game page to pull chessVariant from context, compute selectedVariant, render variant info in status panels, display "Queueing for …" variant text in search overlay, and conditionally render ChessVariantSelector in game mode selection area.
AI Personality Modal
frontend/app/components/matchmaking/AiPersonalityModal.tsx
Updates modal to read chessVariant from context, resolve via getChessVariantById, display selected variant details (label, description, avg game time) in new section; revises header and subtitle copy.
Testing Infrastructure
frontend/package.json, frontend/vitest.setup.ts
Adds @testing-library/jest-dom and @testing-library/react to devDependencies; configures Vitest to load Jest-DOM matchers.

Sequence Diagram

sequenceDiagram
    actor User
    participant Selector as ChessVariantSelector
    participant Context as MatchmakingContext
    participant Page as page.tsx
    participant Modal as AiPersonalityModal

    User->>Selector: Click variant button
    Selector->>Selector: Highlight selected variant
    Selector->>Context: onSelect(variantId)
    Context->>Context: setChessVariant(variantId)
    Context->>Page: Context updates
    Context->>Modal: Context updates
    Page->>Page: Rerender with variant info<br/>(status panel, queue overlay)
    Modal->>Modal: Rerender with variant details<br/>(label, description, avg time)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Four variants click and gleam so bright,
Standard, rapid, blitz—what a sight!
Context whispers, buttons glow,
Selector dances, let variants flow! ♟️✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: implementing a matchmaking flow and chess variant selection feature across multiple components.
Linked Issues check ✅ Passed The PR fully addresses issue #583 requirements: chess variant selector UI is implemented with design patterns, unit tests are included, and the feature is integrated into the frontend.
Out of Scope Changes check ✅ Passed All changes align with the stated objectives: variant selector component, integration into matchmaking context, testing infrastructure, and related UI updates are all within scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@drips-wave

drips-wave Bot commented Apr 24, 2026

Copy link
Copy Markdown

@Dantama022 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai coderabbitai Bot left a comment

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/app/page.tsx (1)

185-188: ⚠️ Potential issue | 🟠 Major

Selected chess variant isn't forwarded to joinMatchmaking.

chessVariant is destructured from the matchmaking context (line 42), displayed in the status panel/overlay, but joinMatchmaking(aiPersonality) on line 187 ignores it entirely. Per the PR description ("game parameters are passed to the matchmaking context"), the variant is expected to influence queueing/time controls — otherwise a user picking "Blitz" vs "Standard" ends up in the same queue with the same parameters.

The joinMatchmaking hook currently accepts only aiPersonality and sends { ai_personality } to the API. To fix:

  1. Extend the hook signature to accept chessVariant
  2. Update the API payload to include the variant parameter
  3. Update the call on line 187 to pass the variant
🛠 Suggested adjustment
  const handlePersonalityConfirm = () => {
    setIsPersonalityModalOpen(false);
-   joinMatchmaking(aiPersonality);
+   joinMatchmaking(aiPersonality, chessVariant);
  };

…and extend useMatchmaking's joinMatchmaking signature and API payload accordingly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/page.tsx` around lines 185 - 188, handlePersonalityConfirm
currently calls joinMatchmaking(aiPersonality) but ignores the selected
chessVariant from the matchmaking context; update the useMatchmaking hook so
joinMatchmaking accepts a second parameter (chessVariant) and include that value
in the API payload (e.g., send chess_variant or variant alongside
ai_personality), update the joinMatchmaking signature/implementation in
useMatchmaking to add this field and adjust any related types, and finally
change handlePersonalityConfirm to call joinMatchmaking(aiPersonality,
chessVariant).
🧹 Nitpick comments (6)
frontend/app/page.tsx (1)

230-239: Minor: use an en-dash or localized separator for variant/time.

{selectedVariant.label} / {selectedVariant.averageGameTime} reads fine, but an en-dash () or "·" renders cleaner between a name and duration and avoids confusion with literal slashes in values like 10-15 min. Optional polish.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/page.tsx` around lines 230 - 239, Replace the literal slash
between the variant label and its average game time with a cleaner separator;
update the JSX that renders {selectedVariant.label} /
{selectedVariant.averageGameTime} to use an en-dash or a localized separator
(e.g., " – " or " · ") by composing the display string from
selectedVariant.label and selectedVariant.averageGameTime (or via a small helper
if localization is needed) so the UI shows "Variant: {selectedVariant.label} –
{selectedVariant.averageGameTime}" instead of using "/".
frontend/components/ChessVariantSelector.tsx (1)

41-50: Consider radio-group semantics for single-select variants.

Since exactly one variant can be selected at a time, a radiogroup is a closer semantic fit than aria-pressed toggle buttons. This would also give keyboard users arrow-key navigation expected for radio groups (assistive tech convention).

♻️ Sketch of the change
-      <div className="grid gap-3 sm:grid-cols-2">
+      <div role="radiogroup" aria-labelledby="match-variant-heading" className="grid gap-3 sm:grid-cols-2">
         {CHESS_VARIANTS.map((variant) => {
           const isSelected = variant.id === selectedVariant;
           return (
             <button
               key={variant.id}
               type="button"
+              role="radio"
+              aria-checked={isSelected}
               onClick={() => onSelect(variant.id)}
-              aria-pressed={isSelected}

Note: tests currently assert aria-pressed, so they would need to be updated in tandem. Happy to leave the current pattern if you prefer to ship as-is.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/components/ChessVariantSelector.tsx` around lines 41 - 50, The
component ChessVariantSelector uses toggle buttons with aria-pressed for single
selection; change it to a proper radio-group pattern: render the list as a
container with role="radiogroup" and each item as role="radio" (or use native
<input type="radio">) instead of aria-pressed, update the selection logic to use
aria-checked (or checked for inputs) driven by selectedVariant, and keep using
onSelect(variant.id) in the click/keyboard handlers; update tests that assert
aria-pressed to assert radio semantics (role="radio" + aria-checked or checked)
and ensure arrow-key navigation works for the radiogroup.
frontend/app/components/matchmaking/AiPersonalityModal.tsx (1)

112-152: Missing dialog semantics / focus management on this modal.

The PR advertises accessibility improvements and you've correctly added role="dialog" + aria-modal="true" + aria-label to the searching and reconnecting overlays in frontend/app/page.tsx (lines 252, 282). For consistency, this modal's root (line 113) should have the same, and ideally Escape-to-close plus initial focus moved into the panel (not the backdrop). Scope-wise it touches pre-existing code, so feel free to defer.

♿ Suggested minimal additions
-    <div className="fixed inset-0 z-50 flex items-center justify-center">
+    <div
+      className="fixed inset-0 z-50 flex items-center justify-center"
+      role="dialog"
+      aria-modal="true"
+      aria-labelledby="match-setup-heading"
+    >
       {/* Backdrop */}
       <div
         className="fixed inset-0 bg-black/70 backdrop-blur-sm"
         onClick={onClose}
+        aria-hidden="true"
       />

And add id="match-setup-heading" to the <h2>Finalize Match Setup</h2>.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/components/matchmaking/AiPersonalityModal.tsx` around lines 112
- 152, In AiPersonalityModal, add proper dialog semantics and basic
focus/keyboard handling: give the modal root div (the outer fixed container)
role="dialog", aria-modal="true" and an aria-label (e.g., "Match setup dialog");
add id="match-setup-heading" to the <h2>Finalize Match Setup</h2>; move initial
focus into the modal panel by making the modal panel element focusable (e.g.,
tabIndex={-1}) and programmatically focus it on mount (useEffect), and add a
keydown handler on mount that closes the modal on Escape (calling onClose) and
cleans up on unmount. Ensure the backdrop click behavior remains unchanged and
that focus is restored when the modal closes.
frontend/vitest.config.ts (1)

4-15: Consider adding @vitejs/plugin-react for proper JSX transform alignment.

Your tsconfig.json has "jsx": "preserve", which means Vitest's esbuild will handle JSX transformation without explicit React configuration. This works for most tests, but adding @vitejs/plugin-react ensures JSX transform semantics are fully aligned with Next.js/React 19 conventions, especially if tests render hooks or complex components.

Optional addition
 import path from "path";
 import { defineConfig } from "vitest/config";
+import react from "@vitejs/plugin-react";

 export default defineConfig({
+  plugins: [react()],
   test: {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/vitest.config.ts` around lines 4 - 15, Update the Vitest config to
include the React plugin so JSX transforms match Next.js/React 19 semantics:
import and invoke `@vitejs/plugin-react` and add it to the exported defineConfig
plugins array (i.e., add the plugin to the config object returned by
defineConfig used in frontend/vitest.config.ts) so tests use the same JSX
transform as your app; ensure the plugin is installed as a dependency.
frontend/package.json (2)

10-10: LGTM on the test script.

vitest run is the correct non-watch invocation for CI. Consider also adding companion scripts ("test:watch": "vitest", "test:coverage": "vitest run --coverage") to align with common workflows, but not required.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/package.json` at line 10, The package.json currently defines only
the CI test script ("test": "vitest run"); add complementary NPM scripts to
support common workflows by adding "test:watch" that runs vitest in watch mode
(e.g., "vitest") and "test:coverage" that runs vitest with coverage enabled
(e.g., "vitest run --coverage") alongside the existing "test" entry so
maintainers can run tests interactively and collect coverage locally.

9-9: Add ignores entry to eslint.config.mjs to exclude build artifacts.

The eslint.config.mjs does not define an ignores entry. Without it, running eslint . will traverse .next/, out/, coverage/, and generated files, slowing down CI and potentially surfacing errors from generated code. Add an ignores configuration:

const eslintConfig = [
  {
    ignores: ['.next/**', 'out/**', 'coverage/**', 'next-env.d.ts'],
  },
  ...compat.extends("next/core-web-vitals", "next/typescript"),
];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/package.json` at line 9, Add an "ignores" entry to the
eslint.config.mjs configuration so ESLint skips build and generated artifacts;
update the top-level eslintConfig (the array exported from eslint.config.mjs) to
include an object with ignores:
['.next/**','out/**','coverage/**','next-env.d.ts'] before spreading the
compat.extends("next/core-web-vitals","next/typescript") entry so running the
"lint" script (eslint .) won't traverse generated files.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@frontend/app/page.tsx`:
- Around line 185-188: handlePersonalityConfirm currently calls
joinMatchmaking(aiPersonality) but ignores the selected chessVariant from the
matchmaking context; update the useMatchmaking hook so joinMatchmaking accepts a
second parameter (chessVariant) and include that value in the API payload (e.g.,
send chess_variant or variant alongside ai_personality), update the
joinMatchmaking signature/implementation in useMatchmaking to add this field and
adjust any related types, and finally change handlePersonalityConfirm to call
joinMatchmaking(aiPersonality, chessVariant).

---

Nitpick comments:
In `@frontend/app/components/matchmaking/AiPersonalityModal.tsx`:
- Around line 112-152: In AiPersonalityModal, add proper dialog semantics and
basic focus/keyboard handling: give the modal root div (the outer fixed
container) role="dialog", aria-modal="true" and an aria-label (e.g., "Match
setup dialog"); add id="match-setup-heading" to the <h2>Finalize Match
Setup</h2>; move initial focus into the modal panel by making the modal panel
element focusable (e.g., tabIndex={-1}) and programmatically focus it on mount
(useEffect), and add a keydown handler on mount that closes the modal on Escape
(calling onClose) and cleans up on unmount. Ensure the backdrop click behavior
remains unchanged and that focus is restored when the modal closes.

In `@frontend/app/page.tsx`:
- Around line 230-239: Replace the literal slash between the variant label and
its average game time with a cleaner separator; update the JSX that renders
{selectedVariant.label} / {selectedVariant.averageGameTime} to use an en-dash or
a localized separator (e.g., " – " or " · ") by composing the display string
from selectedVariant.label and selectedVariant.averageGameTime (or via a small
helper if localization is needed) so the UI shows "Variant:
{selectedVariant.label} – {selectedVariant.averageGameTime}" instead of using
"/".

In `@frontend/components/ChessVariantSelector.tsx`:
- Around line 41-50: The component ChessVariantSelector uses toggle buttons with
aria-pressed for single selection; change it to a proper radio-group pattern:
render the list as a container with role="radiogroup" and each item as
role="radio" (or use native <input type="radio">) instead of aria-pressed,
update the selection logic to use aria-checked (or checked for inputs) driven by
selectedVariant, and keep using onSelect(variant.id) in the click/keyboard
handlers; update tests that assert aria-pressed to assert radio semantics
(role="radio" + aria-checked or checked) and ensure arrow-key navigation works
for the radiogroup.

In `@frontend/package.json`:
- Line 10: The package.json currently defines only the CI test script ("test":
"vitest run"); add complementary NPM scripts to support common workflows by
adding "test:watch" that runs vitest in watch mode (e.g., "vitest") and
"test:coverage" that runs vitest with coverage enabled (e.g., "vitest run
--coverage") alongside the existing "test" entry so maintainers can run tests
interactively and collect coverage locally.
- Line 9: Add an "ignores" entry to the eslint.config.mjs configuration so
ESLint skips build and generated artifacts; update the top-level eslintConfig
(the array exported from eslint.config.mjs) to include an object with ignores:
['.next/**','out/**','coverage/**','next-env.d.ts'] before spreading the
compat.extends("next/core-web-vitals","next/typescript") entry so running the
"lint" script (eslint .) won't traverse generated files.

In `@frontend/vitest.config.ts`:
- Around line 4-15: Update the Vitest config to include the React plugin so JSX
transforms match Next.js/React 19 semantics: import and invoke
`@vitejs/plugin-react` and add it to the exported defineConfig plugins array
(i.e., add the plugin to the config object returned by defineConfig used in
frontend/vitest.config.ts) so tests use the same JSX transform as your app;
ensure the plugin is installed as a dependency.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 610e4873-1272-4656-a18c-543ee31248c1

📥 Commits

Reviewing files that changed from the base of the PR and between 85357b1 and 119ac59.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (9)
  • frontend/app/components/matchmaking/AiPersonalityModal.tsx
  • frontend/app/page.tsx
  • frontend/components/ChessVariantSelector.tsx
  • frontend/components/__tests__/ChessVariantSelector.test.tsx
  • frontend/context/matchmakingContext.tsx
  • frontend/lib/chessVariants.ts
  • frontend/package.json
  • frontend/vitest.config.ts
  • frontend/vitest.setup.ts

@gabito1451

gabito1451 commented Apr 26, 2026

Copy link
Copy Markdown
Contributor

please resolve conflicts @Dantama022

@Dantama022

Copy link
Copy Markdown
Contributor Author

@gabito1451 on it

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (1)
frontend/context/walletContext.tsx (1)

120-336: Out-of-scope change for this PR.

This wallet context refactor (Freighter typings, getFreighter helper, Promise<unknown> typings, Soroban placeholder) doesn't appear in the PR description, which is scoped to the chess variant selector and matchmaking flow (issue #583). Consider splitting it into a dedicated PR so reviewers and the changelog can attribute these wallet/typing improvements correctly, and so a regression here doesn't block the matchmaking feature.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/context/walletContext.tsx` around lines 120 - 336, The changes to
the wallet context (introducing FreighterLike typings, getFreighter helper,
signWithFreighter, Promise<unknown> return types, and the invokeSorobanContract
placeholder) are unrelated to the chess matchmaking work and should be split out
into a dedicated PR; revert or remove these walletContext.tsx changes from this
branch and instead create a new branch/PR that contains the refactor (files and
symbols to isolate: FreighterLike type, getFreighter(), signWithFreighter(),
connectWallet(), invokeSorobanContract(), and related type changes to
AppContextType), so the current PR only contains chess variant selector and
matchmaking changes and reviewers can review the wallet refactor independently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@frontend/context/walletContext.tsx`:
- Around line 120-336: The changes to the wallet context (introducing
FreighterLike typings, getFreighter helper, signWithFreighter, Promise<unknown>
return types, and the invokeSorobanContract placeholder) are unrelated to the
chess matchmaking work and should be split out into a dedicated PR; revert or
remove these walletContext.tsx changes from this branch and instead create a new
branch/PR that contains the refactor (files and symbols to isolate:
FreighterLike type, getFreighter(), signWithFreighter(), connectWallet(),
invokeSorobanContract(), and related type changes to AppContextType), so the
current PR only contains chess variant selector and matchmaking changes and
reviewers can review the wallet refactor independently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 581d202c-d0a4-4020-9f69-7027f9beebbe

📥 Commits

Reviewing files that changed from the base of the PR and between 119ac59 and ff851b7.

📒 Files selected for processing (5)
  • frontend/context/walletContext.tsx
  • frontend/eslint.config.mjs
  • frontend/hook/useCheatDetection.ts
  • frontend/tailwind.config.ts
  • frontend/types/json.d.ts
✅ Files skipped from review due to trivial changes (3)
  • frontend/types/json.d.ts
  • frontend/eslint.config.mjs
  • frontend/tailwind.config.ts

@Dantama022

Copy link
Copy Markdown
Contributor Author

@gabito1451 conflicts resolved

@gabito1451 gabito1451 merged commit 45fddfe into OpenKnight-Foundation:main Apr 28, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Frontend: Chess variant selector UI

2 participants