fix(offline): unlock finished hotseat games + raise entry budget to 120
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m5s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 2m47s

- source: a finished hotseat game is never locked (its final rack is
  shown for review, not an Unlock button); guard locked on !isOver.
- bundle-size: raise the app-entry budget 115 -> 120 KB for the hotseat
  UI (PIN pad, roster, host menu), which lives in the always-loaded New
  Game / Game / Lobby screens; the offline engine + PIN hashing stay lazy.
This commit is contained in:
Ilia Denisov
2026-07-07 12:04:46 +02:00
parent baa9961c3e
commit 903de7d41d
3 changed files with 13 additions and 8 deletions
+4 -3
View File
@@ -120,13 +120,14 @@ describe('LocalSource hotseat', () => {
expect(await src.verifyHostPin('local:h7', '0000')).toBe(false);
});
it('persists a naturally finished game (scoreless run) as finished', async () => {
it('persists a naturally finished game (scoreless run) as finished, and unlocked', async () => {
const src = await hotseat('local:h8', [
{ kind: 'human', name: 'A' },
{ kind: 'human', name: 'B' },
{ kind: 'human', name: 'A', pin: await newLock('1111') },
{ kind: 'human', name: 'B', pin: await newLock('2222') },
]);
for (let i = 0; i < 6; i++) await src.pass('local:h8');
const st = await src.gameState('local:h8');
expect(st.game.status).toBe('finished');
expect(st.locked).toBeFalsy(); // a finished game shows its final rack, never an unlock button
});
});
+2 -1
View File
@@ -418,7 +418,8 @@ export class LocalSource implements GameLoopSource {
private stateView(entry: Live): StateView {
const hotseat = !!entry.record.hotseat;
const seat = hotseat ? entry.game.currentPlayer : this.humanSeat(entry);
const locked = hotseat && !!entry.record.seats[seat]?.pin && entry.unlockedSeat !== seat;
// A finished game is never locked — its final rack is shown for review, not an unlock button.
const locked = !entry.game.isOver && hotseat && !!entry.record.seats[seat]?.pin && entry.unlockedSeat !== seat;
const rack = locked ? [] : entry.game.handOf(seat).map((t) => indexToLetter(entry.record.variant, t));
return {
game: this.gameView(entry),