Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
30d3aa0
fix(ranking): make Elo rounding symmetric
Poooel Aug 2, 2026
87413bf
feat(ranking): add ranked match persistence
Poooel Aug 2, 2026
5dbc598
feat(matchmaking): reserve ranked assignments
Poooel Aug 2, 2026
127803d
feat(matchmaking): activate ranked assignments
Poooel Aug 2, 2026
6b8475d
feat(ranking): settle ranked results once
Poooel Aug 2, 2026
0994440
feat(matchmaking): add ranked queue experience
Poooel Aug 2, 2026
9a51100
feat(matchmaking): apply rating-aware queue timing
Poooel Aug 2, 2026
c869a3e
feat(matchmaking): recover expired assignments
Poooel Aug 2, 2026
c15b791
feat(ranking): add explicit resignation
Poooel Aug 2, 2026
c86e0b7
perf(matchmaking): avoid database reads while queued
Poooel Aug 2, 2026
d949414
feat(ranking): record lifecycle metrics
Poooel Aug 2, 2026
bc2e3dd
feat(ranking): label ranked game rooms
Poooel Aug 2, 2026
553771e
feat(ranking): gate matchmaking rollout
Poooel Aug 2, 2026
85daaa7
fix(identity): keep transfer modal closed
Poooel Aug 2, 2026
edbb41c
fix(identity): initialize in background
Poooel Aug 2, 2026
1c35cad
fix(layout): mount sidebar actions before paint
Poooel Aug 2, 2026
dc261b0
refactor(identity): remove startup gate
Poooel Aug 2, 2026
e65a2d6
fix(game): wait for identity before opening socket
Poooel Aug 2, 2026
e2f9aa4
test(e2e): wait for background identity setup
Poooel Aug 2, 2026
39cb51f
fix(home): prevent ranked button layout shift
Poooel Aug 2, 2026
3957c86
feat(language): cycle through chinese locale
Poooel Aug 2, 2026
dfc588b
fix(game): preserve settings across language changes
Poooel Aug 2, 2026
7e36025
Revert "fix(game): preserve settings across language changes"
Poooel Aug 2, 2026
a371393
fix(language): preserve current route
Poooel Aug 2, 2026
eafaf65
feat(language): switch locales without reloading
Poooel Aug 2, 2026
2c9cba6
fix(button): show pointer for interactive controls
Poooel Aug 2, 2026
f73fe4a
fix(button): show pointer for icon controls
Poooel Aug 2, 2026
c51fdd1
fix(footer): update repository link
Poooel Aug 2, 2026
aebaa33
fix(identity): generate transfer code on open
Poooel Aug 2, 2026
3391eeb
fix(identity): align revoke checkboxes
Poooel Aug 2, 2026
51eaa19
fix(identity): simplify recovery phrase actions
Poooel Aug 2, 2026
844aa5f
feat(matchmaking): improve queue wait feedback
Poooel Aug 2, 2026
21e4552
feat(matchmaking): show queue population
Poooel Aug 2, 2026
53fb72a
fix(matchmaking): cancel queue on page exit
Poooel Aug 2, 2026
ba34206
feat(matchmaking): require match acceptance
Poooel Aug 2, 2026
98f48da
feat(matchmaking): allow declining matches
Poooel Aug 2, 2026
28ef863
fix(matchmaking): avoid transient error flash
Poooel Aug 2, 2026
9389294
feat(game): explain forfeit outcomes
Poooel Aug 2, 2026
2b91690
fix(sidebar): move ranked details below round
Poooel Aug 2, 2026
ac8c3d0
feat(ranked): rematch the same opponent
Poooel Aug 2, 2026
8b3d2f2
fix(sidebar): keep ranked details at bottom
Poooel Aug 2, 2026
b0f7207
fix(board): show pointer for playable columns
Poooel Aug 2, 2026
437d4b5
fix(spectator): hide end game actions
Poooel Aug 2, 2026
986171e
feat(ux): show match rating gap and transfer QR
Poooel Aug 2, 2026
d0cf3c9
fix(ranked): show settled rating after every result
Poooel Aug 2, 2026
9f12123
fix(sidebar): start closed on mobile
Poooel Aug 2, 2026
48c7fa6
feat(ranked): add turn timer and timeout forfeits
Poooel Aug 3, 2026
1f91e7c
fix(dev): reset local state before startup
Poooel Aug 3, 2026
b919715
fix(ranked): finalize timeout handling
Poooel Aug 3, 2026
554e7c1
fix(ranked): guarantee settlement and harden match lifecycle
Poooel Aug 3, 2026
bf13d02
fix(ranked): always show play ranked button
Poooel Aug 7, 2026
f2f4361
fix(ranked): reconnect websockets and stop disconnect-notice flicker
Poooel Aug 7, 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
41 changes: 29 additions & 12 deletions apps/front/src/components/App.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as React from 'react'
import { BrowserRouter } from 'react-router-dom'
import { getPathLanguage } from '../translations'
import { useTranslation } from 'react-i18next'
import { BrowserRouter, useLocation } from 'react-router-dom'
import { DEFAULT_LANGUAGE, getPathLanguage } from '../translations'
import { ensurePlayerIdentity } from '../utils/playerIdentity'
import { Language } from './Language'
import { PlayerIdentityGate } from './PlayerIdentityGate'
import { PlayerIdentityTransfer } from './PlayerIdentityTransfer'
import { Router } from './Router'
import {
Expand All @@ -13,11 +14,31 @@ import {
} from './SideBar'
import { Theme } from './Theme'

function LanguageSync() {
const { pathname } = useLocation()
const { i18n } = useTranslation()

React.useEffect(() => {
const language = getPathLanguage(pathname) ?? DEFAULT_LANGUAGE
document.documentElement.lang = language
void i18n.changeLanguage(language)
}, [i18n, pathname])

return null
}

export function App() {
const mainContentRef = React.useRef<React.ElementRef<'div'>>(null)

React.useEffect(() => {
void ensurePlayerIdentity().catch((error) => {
console.error('Failed to initialize player identity.', error)
})
}, [])

return (
<BrowserRouter basename={getPathLanguage()}>
<BrowserRouter>
<LanguageSync />
<div className='bg-slate-50 text-slate-900 transition-colors duration-150 ease-in-out dark:bg-slate-900 dark:text-slate-200'>
<SideBarLayout>
<SideBarContainer
Expand All @@ -31,14 +52,10 @@ export function App() {
/>

<MainContent ref={mainContentRef}>
<PlayerIdentityGate>
<>
<SideBarActions>
<PlayerIdentityTransfer />
</SideBarActions>
<Router />
</>
</PlayerIdentityGate>
<SideBarActions>
<PlayerIdentityTransfer />
</SideBarActions>
<Router />
</MainContent>
</SideBarLayout>
</div>
Expand Down
2 changes: 1 addition & 1 deletion apps/front/src/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function Button<E extends React.ElementType = typeof defaultElement>({
<Component
{...props}
className={clsx(
'flex flex-row items-center gap-2 rounded-md text-center font-medium text-slate-900 transition-colors duration-100 disabled:opacity-50 dark:text-slate-50',
'flex cursor-pointer flex-row items-center gap-2 rounded-md text-center font-medium text-slate-900 transition-colors duration-100 disabled:cursor-not-allowed disabled:opacity-50 dark:text-slate-50',
{
'justify-center': center,
'justify-start': !center,
Expand Down
2 changes: 1 addition & 1 deletion apps/front/src/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function Footer() {
<div className='flex flex-row justify-center gap-4'>
<a
className='text-slate-900 transition-all hover:text-slate-900/80 dark:text-slate-200 dark:hover:text-slate-50/80'
href='https://github.com/Poooel/knucklebones'
href='https://github.com/osselets/knucklebones'
target='_blank'
rel='noreferrer'
>
Expand Down
18 changes: 18 additions & 0 deletions apps/front/src/components/Game.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { useIsOnMobile } from '../hooks/detectDevice'
import { useNoIndex } from '../hooks/useNoIndex'
import { Button } from './Button'
import { useGameWhileLoading } from './GameContext'
import { GameOutcome } from './GameOutcome'
import { HowToPlayModal } from './HowToPlay'
import { Loading } from './Loading'
import { OutcomeHistory } from './OutcomeHistory'
import { PlayerOneBoard, PlayerTwoBoard } from './PlayerBoard'
import { QRCodeModal } from './QRCode'
import { RankedMatchInfo } from './RankedMatchInfo'
import { ReconnectNotice } from './ReconnectNotice'
import { ResignGame } from './ResignGame'
import { SideBarActions } from './SideBar'
import { WarningToast } from './WarningToast'

export function Game() {
const gameStore = useGameWhileLoading()
const { t } = useTranslation()
const isOnMobile = useIsOnMobile()
const gameRef = React.useRef<React.ElementRef<'div'>>(null)
useNoIndex()
Expand All @@ -22,6 +27,17 @@ export function Game() {
return <Loading />
}

if (gameStore.status === 'identity-error') {
return (
<div className='flex flex-col items-center justify-center gap-4 p-4 text-center'>
<p className='text-lg'>{gameStore.errorMessage}</p>
<Button size='medium' onClick={gameStore.retryIdentity}>
{t('identity.retry')}
</Button>
</div>
)
}

const { errorMessage, clearErrorMessage } = gameStore

// Pas tip top je trouve, mais virtuellement ça marche
Expand All @@ -33,6 +49,8 @@ export function Game() {
<HowToPlayModal />
<QRCodeModal />
<OutcomeHistory />
<ResignGame />
<RankedMatchInfo />
{isOnMobile && gameOutcome}
</SideBarActions>
<div ref={gameRef} className='flex flex-col items-center justify-around'>
Expand Down
4 changes: 2 additions & 2 deletions apps/front/src/components/GameContext/GameContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ export function useGameWhileLoading() {
export function useGame() {
const context = useGameWhileLoading()

if (context === null) {
if (context === null || context.status === 'identity-error') {
throw new Error(
'`GameContext` should not be used while the game is still loading'
'`GameContext` should not be used while the game is loading or its identity is unresolved'
)
}

Expand Down
Loading
Loading