Skip to content

fix(crazygames): guest username on logout, hide fullscreen, in-game pop-ups#4538

Merged
evanpelle merged 4 commits into
mainfrom
crazygames
Jul 8, 2026
Merged

fix(crazygames): guest username on logout, hide fullscreen, in-game pop-ups#4538
evanpelle merged 4 commits into
mainfrom
crazygames

Conversation

@evanpelle

Copy link
Copy Markdown
Collaborator

Addresses four requests from the CrazyGames platform team.
Supersedes #4520 (closed when its branch was renamed to crazygames).

1. Username reverts to guest on CrazyGames logout

When a player logged into their CrazyGames account and then logged out, the username stayed the CrazyGames name. It now reverts to a fresh guest (AnonXXX) name. A crazyGamesLoggedIn flag guards this so it only fires on a real login→logout transition (not on the initial "not logged in" state, which would otherwise wipe a stored name).

2. Fullscreen button hidden on CrazyGames

CrazyGames provides its own fullscreen control in the game frame, so our in-game fullscreen button is now hidden when running on CrazyGames (and unchanged everywhere else).

3. Native browser prompts → in-game pop-ups

The showInGameConfirm() / showInGameAlert() helpers (promise-based, callable from non-Lit contexts like WebSocket/popstate handlers) drive the existing shared <confirm-dialog> component, so styling stays consistent with the rest of the app. Every native confirm()/alert() shown during gameplay now uses it:

  • Exit-game confirmation (right sidebar + browser-back path)
  • Kick-player confirmation
  • Host-left notice (dispatches leave-lobby only after dismiss, preserving the old blocking UX)
  • Connection-refused notice (also removes a stale // TODO: make this a modal)

4. gameplayStop when Settings menu / pop-up is open

  • The pop-up helpers report gameplayStop() while shown and gameplayStart() on dismiss.
  • Settings + Graphics Settings modals now report gameplayStop whenever the menu is open — previously it only fired when the modal also paused the sim (singleplayer / lobby-creator), so regular multiplayer players never triggered it. Also fixes graphics-settings so gameplay resumes correctly on close for those players.

Scope

Per request, this covers in-game dialogs only. The main-menu native alerts (store/checkout, account, subscriptions, friends, login) were intentionally left as-is.

Testing

  • tsc --noEmit, eslint, and prettier --check all pass.
  • Verified the confirm and alert pop-ups render correctly in the running app (shared <confirm-dialog>, danger/warning variants), resolve their promises, and tear down their portal.
  • CrazyGames-iframe-only behaviors (username revert, gameplayStop, fullscreen hiding) are verified by code inspection — they require running inside the actual CrazyGames frame.

🤖 Generated with Claude Code

evanpelle and others added 2 commits July 6, 2026 18:31
…op-ups

Addresses four CrazyGames platform requests:

- Username reverts to a fresh guest (AnonXXX) name when the player logs
  out of their CrazyGames account, instead of keeping the account name.
- Hide our in-game fullscreen button on CrazyGames (the frame provides one).
- Replace native browser confirm()/alert() dialogs shown during gameplay
  with an in-game pop-up (new InGameModal): exit-game confirmation, kick
  confirmation, host-left, and connection-refused.
- Report gameplayStop to CrazyGames whenever the Settings menu or a pop-up
  is open (previously only when it also paused the sim), resuming on close.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Instead of a hand-rolled modal, the showInGameConfirm/showInGameAlert
helpers now drive the existing <confirm-dialog> component so the styling
stays consistent with the rest of the app. Keeps the promise-based API
(callable from non-Lit contexts) and the gameplayStop/Start reporting.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR replaces browser dialogs with in-game modals, updates several client flows to await them, and changes CrazyGames gameplay, username storage, and fullscreen behavior.

Changes

In-game modal and CrazyGames flow updates

Layer / File(s) Summary
InGameModal helper module
src/client/InGameModal.ts
Adds promise-based in-game confirm and alert helpers that create <confirm-dialog> elements, stop/resume gameplay, and resolve from dialog events.
Replace alert() with showInGameAlert in error handlers
src/client/ClientGameRunner.ts, src/client/Transport.ts, resources/lang/en.json
Host-left and connection-refused error handling now use showInGameAlert, with the new localized connection-refused message supporting the WebSocket close path.
Replace confirm() with showInGameConfirm in navigation, exit, and kick flows
src/client/Main.ts, src/client/hud/layers/GameRightSidebar.ts, src/client/hud/layers/PlayerModerationModal.ts
Leave-game, sidebar exit, and kick-player actions now await showInGameConfirm() before continuing their existing actions.
Gameplay pause, username storage, and fullscreen rules
src/client/hud/layers/GraphicsSettingsModal.ts, src/client/hud/layers/SettingsModal.ts, src/client/UsernameInput.ts, src/client/hud/layers/GameRightSidebar.ts
CrazyGames gameplay stop/start calls are separated from simulation pause intent, CrazyGames usernames are no longer persisted in local storage, and the fullscreen toggle is hidden on CrazyGames.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant UIComponent
  participant InGameModal
  participant CrazyGamesSDK
  participant ConfirmDialog

  UIComponent->>InGameModal: showInGameConfirm(message) / showInGameAlert(message)
  InGameModal->>CrazyGamesSDK: gameplayStop()
  InGameModal->>ConfirmDialog: render dialog
  ConfirmDialog-->>InGameModal: confirm / cancel
  InGameModal->>CrazyGamesSDK: gameplayStart()
  InGameModal-->>UIComponent: resolve true / false
Loading

Possibly related PRs

Suggested labels: UI/UX

Poem

A dialog blooms inside the game,
No browser alert is quite the same.
Confirm, close, or let it be,
The gameplay pauses carefully. 🎮
Username slips back to guest-light air,
While CrazyGames hides the fullscreen chair.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly names the main CrazyGames UX changes: username reset, fullscreen hiding, and in-game pop-ups.
Description check ✅ Passed The description matches the changeset and explains the CrazyGames requests, scope, and behavior changes well.

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.

@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.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/client/hud/layers/GraphicsSettingsModal.ts (1)

169-183: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract shared pauseGame logic to avoid duplication with SettingsModal.ts.

The pauseGame method is now identical to the one in SettingsModal.ts (lines 110-124). Both implement the same CrazyGames + PauseGameIntentEvent gating. Extracting a shared helper prevents future divergence if the pause-reporting logic changes.

The logic itself is correct: gameplayStop() fires unconditionally on open, gameplayStart() fires on close unless the game was already paused, and PauseGameIntentEvent remains gated by shouldPause && !wasPausedWhenOpened. The CrazyGames SDK's internal isGameplayActive / isReady() guards make the unconditional calls safe.

♻️ Proposed extraction into a shared helper
+// e.g. in a shared module imported by both modals
+export function pauseGameForModal(
+  pause: boolean,
+  shouldPause: boolean,
+  wasPausedWhenOpened: boolean,
+  eventBus: EventBus,
+) {
+  if (pause) {
+    crazyGamesSDK.gameplayStop();
+  } else if (!wasPausedWhenOpened) {
+    crazyGamesSDK.gameplayStart();
+  }
+  if (shouldPause && !wasPausedWhenOpened) {
+    eventBus.emit(new PauseGameIntentEvent(pause));
+  }
+}

Then in both modals:

 private pauseGame(pause: boolean) {
-  // CrazyGames: report gameplay as stopped whenever this settings menu is open,
-  // and resumed when it closes — unless the game was already paused when opened.
-  if (pause) {
-    crazyGamesSDK.gameplayStop();
-  } else if (!this.wasPausedWhenOpened) {
-    crazyGamesSDK.gameplayStart();
-  }
-
-  // Only pause the simulation itself when we own the pause (singleplayer or
-  // lobby creator).
-  if (this.shouldPause && !this.wasPausedWhenOpened) {
-    this.eventBus.emit(new PauseGameIntentEvent(pause));
-  }
+  pauseGameForModal(pause, this.shouldPause, this.wasPausedWhenOpened, this.eventBus);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/hud/layers/GraphicsSettingsModal.ts` around lines 169 - 183,
Extract the duplicated pause-reporting logic from
GraphicsSettingsModal.pauseGame and the matching SettingsModal method into a
shared helper so both modals use the same CrazyGames and PauseGameIntentEvent
gating. Move the common behavior into a reusable function or utility that takes
the pause state plus the modal-specific flags such as shouldPause and
wasPausedWhenOpened, then have both pauseGame methods delegate to it to prevent
future divergence.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/client/InGameModal.ts`:
- Around line 16-32: showDialog() currently resumes gameplay unconditionally,
which can override other active pause states. Update the flow in
InGameModal.showDialog so gameplayStart() is only called through the shared
pause-management mechanism used across SettingsModal, GraphicsSettingsModal,
GameRightSidebar, and WinModal, rather than directly from this dialog. Ensure
the dialog only releases its own stop request and let the centralized
counter/state decide when gameplay can actually restart.

In `@src/client/Transport.ts`:
- Line 391: The alert text in Transport.showInGameAlert is user-visible but
currently hardcoded as `connection refused: ${event.reason}` instead of going
through translateText(). Update the alert call to use a translation key and pass
event.reason as the interpolated value, following the existing localization
pattern used in src/client/**/* and keeping the logic in Transport or the
surrounding connection-handling code.

In `@src/client/UsernameInput.ts`:
- Around line 101-119: The CrazyGames auth initialization in UsernameInput can
race with a logout and overwrite the guest state after the user has already
logged out. Add a small request/version guard around the getUsername() promise
and the addAuthListener callback so only the latest auth state updates
baseUsername, and make sure the null branch can still reset to genAnonUsername
even if the initial login state has not been marked yet.

---

Nitpick comments:
In `@src/client/hud/layers/GraphicsSettingsModal.ts`:
- Around line 169-183: Extract the duplicated pause-reporting logic from
GraphicsSettingsModal.pauseGame and the matching SettingsModal method into a
shared helper so both modals use the same CrazyGames and PauseGameIntentEvent
gating. Move the common behavior into a reusable function or utility that takes
the pause state plus the modal-specific flags such as shouldPause and
wasPausedWhenOpened, then have both pauseGame methods delegate to it to prevent
future divergence.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8adc37ba-94d6-4241-bbdc-dfabdae9525f

📥 Commits

Reviewing files that changed from the base of the PR and between f78b5c4 and 223b218.

📒 Files selected for processing (9)
  • src/client/ClientGameRunner.ts
  • src/client/InGameModal.ts
  • src/client/Main.ts
  • src/client/Transport.ts
  • src/client/UsernameInput.ts
  • src/client/hud/layers/GameRightSidebar.ts
  • src/client/hud/layers/GraphicsSettingsModal.ts
  • src/client/hud/layers/PlayerModerationModal.ts
  • src/client/hud/layers/SettingsModal.ts

Comment thread src/client/InGameModal.ts
Comment on lines +16 to +32
function showDialog(props: Partial<ConfirmDialog>): Promise<boolean> {
const dialog = document.createElement("confirm-dialog") as ConfirmDialog;
Object.assign(dialog, props);

crazyGamesSDK.gameplayStop();
document.body.appendChild(dialog);

return new Promise<boolean>((resolve) => {
const done = (result: boolean) => {
dialog.remove();
crazyGamesSDK.gameplayStart();
resolve(result);
};
dialog.addEventListener("confirm", () => done(true));
dialog.addEventListener("cancel", () => done(false));
});
}

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether CrazyGames SDK gameplayStart/gameplayStop use a counter or boolean
rg -n "gameplayStart\|gameplayStop" src/client/CrazyGamesSDK.ts -A5 -B2

# Check if SettingsModal/GraphicsSettingsModal also call gameplayStop/gameplayStart
rg -n "gameplayStart\|gameplayStop" src/client/hud/layers/SettingsModal.ts src/client/hud/layers/GraphicsSettingsModal.ts -A3 -B1

Repository: openfrontio/OpenFrontIO

Length of output: 161


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find all gameplayStart/gameplayStop usages
rg -n "gameplayStart|gameplayStop" src -S

# Locate the modal/dialog files involved
fd -a "InGameModal.ts|SettingsModal.ts|GraphicsSettingsModal.ts|CrazyGamesSDK.ts" src

Repository: openfrontio/OpenFrontIO

Length of output: 1892


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the relevant sections with line numbers
sed -n '1,120p' src/client/InGameModal.ts
printf '\n--- CrazyGamesSDK ---\n'
sed -n '170,235p' src/client/CrazyGamesSDK.ts
printf '\n--- SettingsModal ---\n'
sed -n '90,130p' src/client/hud/layers/SettingsModal.ts
printf '\n--- GraphicsSettingsModal ---\n'
sed -n '155,190p' src/client/hud/layers/GraphicsSettingsModal.ts
printf '\n--- WinModal ---\n'
sed -n '210,240p' src/client/hud/layers/WinModal.ts
printf '\n--- GameRightSidebar ---\n'
sed -n '175,215p' src/client/hud/layers/GameRightSidebar.ts

Repository: openfrontio/OpenFrontIO

Length of output: 7838


Avoid resuming gameplay from this dialog alone

showDialog() can still call gameplayStart() while another UI flow is keeping gameplay stopped. This code is only safe if all gameplayStop()/gameplayStart() calls go through one shared counter or centralized pause state; a local counter here will not cover SettingsModal, GraphicsSettingsModal, GameRightSidebar, or WinModal.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/InGameModal.ts` around lines 16 - 32, showDialog() currently
resumes gameplay unconditionally, which can override other active pause states.
Update the flow in InGameModal.showDialog so gameplayStart() is only called
through the shared pause-management mechanism used across SettingsModal,
GraphicsSettingsModal, GameRightSidebar, and WinModal, rather than directly from
this dialog. Ensure the dialog only releases its own stop request and let the
centralized counter/state decide when gameplay can actually restart.

Comment thread src/client/Transport.ts Outdated
Comment on lines 101 to 119
crazyGamesSDK.getUsername().then((username) => {
if (username) {
this.crazyGamesLoggedIn = true;
this.baseUsername = username;
this.validateAndStore();
}
});
crazyGamesSDK.addAuthListener((user) => {
if (user) {
this.crazyGamesLoggedIn = true;
this.baseUsername = user.username;
this.validateAndStore();
} else if (this.crazyGamesLoggedIn) {
// Logged out of CrazyGames — revert to a fresh guest username.
this.crazyGamesLoggedIn = false;
this.baseUsername = genAnonUsername();
this.validateAndStore();
}
});

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect CrazyGamesSDK.getUsername to check whether it returns a live or snapshot username.
ast-grep outline src/client/CrazyGamesSDK.ts --match getUsername --view expanded

Repository: openfrontio/OpenFrontIO

Length of output: 203


🏁 Script executed:

#!/bin/bash
set -euo pipefail

git ls-files 'src/client/**/*' | sed -n '1,200p'
printf '\n--- search CrazyGames symbols ---\n'
rg -n --hidden --glob 'src/client/**/*.{ts,tsx}' 'CrazyGamesSDK|getUsername|addAuthListener|crazyGamesLoggedIn|baseUsername' src/client
printf '\n--- outline UsernameInput ---\n'
ast-grep outline src/client/UsernameInput.ts --view expanded

Repository: openfrontio/OpenFrontIO

Length of output: 13797


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the CrazyGames SDK implementation and any related auth methods.
fd -a 'CrazyGamesSDK' src/client || true
rg -n --hidden --glob 'src/client/**/*.{ts,tsx}' 'class .*CrazyGames|function .*getUsername|getUsername\(|addAuthListener\(' src/client

Repository: openfrontio/OpenFrontIO

Length of output: 1072


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- CrazyGamesSDK.ts (120-170) ---'
sed -n '120,170p' src/client/CrazyGamesSDK.ts | cat -n

printf '\n%s\n' '--- UsernameInput.ts (96-120) ---'
sed -n '96,120p' src/client/UsernameInput.ts | cat -n

Repository: openfrontio/OpenFrontIO

Length of output: 2728


🌐 Web query:

CrazyGames SDK user.getUser semantics returns current user at call time documentation

💡 Result:

The CrazyGames SDK method user.getUser is designed to retrieve the user currently logged into the CrazyGames platform at the time the function is called [1][2]. Official documentation explicitly defines this method as the way to retrieve the "currently logged in" user [1][2]. If no user is logged in at the moment the method is invoked, it returns null [1][2]. Because the method is asynchronous (typically returning a Promise or requiring a callback), it fetches the up-to-date session state from the platform at that exact call time [1][2].

Citations:


Guard the async CrazyGames auth init against logout races. getUsername() can still write a logged-in name after a logout event if that logout lands before the promise settles, and the null auth event is skipped while crazyGamesLoggedIn is still false. Add a small version/request check before updating baseUsername.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/UsernameInput.ts` around lines 101 - 119, The CrazyGames auth
initialization in UsernameInput can race with a logout and overwrite the guest
state after the user has already logged out. Add a small request/version guard
around the getUsername() promise and the addAuthListener callback so only the
latest auth state updates baseUsername, and make sure the null branch can still
reset to genAnonUsername even if the initial login state has not been marked
yet.

@github-project-automation github-project-automation Bot moved this from Triage to Development in OpenFront Release Management Jul 8, 2026
@evanpelle evanpelle modified the milestones: Backlog, v32 Jul 8, 2026
@evanpelle evanpelle marked this pull request as ready for review July 8, 2026 18:40
@evanpelle evanpelle requested a review from a team as a code owner July 8, 2026 18:40
@evanpelle evanpelle merged commit d3ad1f5 into main Jul 8, 2026
15 of 20 checks passed
@evanpelle evanpelle deleted the crazygames branch July 8, 2026 19:02
@github-project-automation github-project-automation Bot moved this from Development to Complete in OpenFront Release Management Jul 8, 2026
evanpelle added a commit that referenced this pull request Jul 8, 2026
…op-ups (#4538)

Addresses four requests from the CrazyGames platform team.
Supersedes #4520 (closed when its branch was renamed to `crazygames`).

## 1. Username reverts to guest on CrazyGames logout
When a player logged into their CrazyGames account and then logged out,
the username stayed the CrazyGames name. It now reverts to a fresh guest
(`AnonXXX`) name. A `crazyGamesLoggedIn` flag guards this so it only
fires on a real login→logout transition (not on the initial "not logged
in" state, which would otherwise wipe a stored name).

## 2. Fullscreen button hidden on CrazyGames
CrazyGames provides its own fullscreen control in the game frame, so our
in-game fullscreen button is now hidden when running on CrazyGames (and
unchanged everywhere else).

## 3. Native browser prompts → in-game pop-ups
The `showInGameConfirm()` / `showInGameAlert()` helpers (promise-based,
callable from non-Lit contexts like WebSocket/popstate handlers) drive
the existing shared `<confirm-dialog>` component, so styling stays
consistent with the rest of the app. Every native `confirm()`/`alert()`
shown **during gameplay** now uses it:
- Exit-game confirmation (right sidebar + browser-back path)
- Kick-player confirmation
- Host-left notice (dispatches leave-lobby only after dismiss,
preserving the old blocking UX)
- Connection-refused notice (also removes a stale `// TODO: make this a
modal`)

## 4. `gameplayStop` when Settings menu / pop-up is open
- The pop-up helpers report `gameplayStop()` while shown and
`gameplayStart()` on dismiss.
- Settings + Graphics Settings modals now report `gameplayStop` whenever
the menu is open — previously it only fired when the modal *also* paused
the sim (singleplayer / lobby-creator), so regular multiplayer players
never triggered it. Also fixes graphics-settings so gameplay resumes
correctly on close for those players.

## Scope
Per request, this covers **in-game dialogs only**. The main-menu native
alerts (store/checkout, account, subscriptions, friends, login) were
intentionally left as-is.

## Testing
- `tsc --noEmit`, `eslint`, and `prettier --check` all pass.
- Verified the confirm and alert pop-ups render correctly in the running
app (shared `<confirm-dialog>`, danger/warning variants), resolve their
promises, and tear down their portal.
- CrazyGames-iframe-only behaviors (username revert, `gameplayStop`,
fullscreen hiding) are verified by code inspection — they require
running inside the actual CrazyGames frame.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Complete

Development

Successfully merging this pull request may close these issues.

1 participant