Skip to content

in game promo#3602

Draft
evanpelle wants to merge 1 commit intov30from
in-game-promo
Draft

in game promo#3602
evanpelle wants to merge 1 commit intov30from
in-game-promo

Conversation

@evanpelle
Copy link
Copy Markdown
Collaborator

If this PR fixes an issue, link it below. If not, delete these two lines.
Resolves #(issue number)

Description:

Describe the PR.

Please complete the following:

  • I have added screenshots for all UI updates
  • I process any text displayed to the user through translateText() and I've added it to the en.json file
  • I have added relevant tests to the test directory
  • I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced

Please put your Discord username so you can be contacted if a bug or regression is found:

DISCORD_USERNAME

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 6, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Replaced single ad slot with three-config AD_TYPES in in-game promos; updated ad enqueue/teardown and render for multiple slots; added bottom-rail load/destroy methods to homepage promos and wired them into client lobby join/leave; removed footer ad element; increased Docker timeout for a dev domain and lengthened dev game creation interval.

Changes

Cohort / File(s) Summary
In-game multi-slot ad
src/client/graphics/layers/InGamePromo.ts
Replaced single bottom-left ad config with AD_TYPES (three {type, selectorId}); enqueue all types via window.ramp.spaAddAds; teardown iterates AD_TYPES calling window.ramp.destroyUnits(type); render() now outputs a flex column-reverse container with three child divs keyed by each selectorId; bottom-rail lifecycle guard changed (bottomRailActivebottomRailDestroyed) and destroyBottomRail() calls window.ramp.destroyUnits("bottom_rail"); init() made a no-op.
Homepage promos — bottom-rail
src/client/HomepagePromos.ts
Removed footer-ad element/state and related constants; added loadBottomRail() and destroyBottomRail() to enqueue/destroy "bottom_rail" via window.ramp (with availability guards); adjusted close() to set this.adLoaded = false and only destroy gutter ads; gutter vertical positioning simplified.
Client lifecycle wiring
src/client/Main.ts
On lobby join, queries homepage-promos and calls loadBottomRail() when present; on lobby leave, calls destroyBottomRail() after removing in-game body class.
Static layout
index.html
Removed <home-footer-ad> from the footer wrapper; footer now renders only <page-footer>.
Docker entrypoint
Dockerfile
For DOMAIN=openfront.dev with non-main SUBDOMAIN, extended the timeout passed to supervisord from 18h to 100h.
Dev server timing
src/core/configuration/DevConfig.ts
Increased DevServerConfig.gameCreationRate() from 5 * 1000 to 50 * 1000.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Homepage as HomepagePromos
    participant InGame as InGamePromo
    participant RAMP as window.ramp
    participant DOM as Browser DOM

    Client->>Homepage: find homepage-promos
    Client->>Homepage: call loadBottomRail()
    Homepage->>RAMP: spaAddAds([{type:"bottom_rail"}])
    RAMP-->>DOM: insert bottom_rail units into selectors

    Note over InGame,RAMP: In-game multi-slot flow
    InGame->>RAMP: spaAddAds(AD_TYPES array)
    RAMP-->>DOM: insert multiple slot units into selectorIds
    DOM-->>InGame: slots available in DOM

    Client->>Homepage: on leave -> call destroyBottomRail()
    Homepage->>RAMP: destroyUnits("bottom_rail")
    InGame->>RAMP: destroyUnits(type) for each AD_TYPES entry
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

Three little slots wake on the screen,
Calls sent out to a quieter machine,
Footer gone, the rail stays bright,
Dev ticks slowed into the night,
Slots spin up, then hush — all right ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description contains only the repository's PR template with placeholders and checkboxes, providing no actual details about the changes made in the pull request. Complete the description section with concrete details about what was changed and why, including specifics about the ad system refactoring, bottom-rail implementation, and related updates.
Title check ❓ Inconclusive The title 'in game promo' is vague and generic, using non-descriptive terms that don't convey meaningful information about the substantial changes in the changeset. Provide a more specific title that describes the main changes, such as 'Replace footer ad with bottom-rail ad and update game creation rate' or 'Refactor ad system to support bottom-rail promotions'.
✅ Passed checks (1 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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


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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/client/graphics/layers/InGamePromo.ts (2)

162-167: Consider single destroyUnits call with array.

The API accepts string | string[] per the type declaration in Main.ts. A single call with all types is cleaner than looping. However, the current pattern matches GutterAds.ts, so this is optional.

♻️ Optional: Use array parameter for single call
     try {
-      for (const { type } of AD_TYPES) {
-        window.ramp.destroyUnits(type);
-      }
+      window.ramp.destroyUnits(AD_TYPES.map(({ type }) => type));
       console.log("successfully destroyed in-game bottom-left ads");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/graphics/layers/InGamePromo.ts` around lines 162 - 167, AD_TYPES
is being iterated to call window.ramp.destroyUnits(type) multiple times; instead
call window.ramp.destroyUnits with an array of types in one call. Replace the
loop over AD_TYPES with a single call passing AD_TYPES.map(item => item.type)
(or otherwise extract the string[] of types) to window.ramp.destroyUnits,
keeping the same try/catch and console logging; reference
window.ramp.destroyUnits and AD_TYPES (and the type signature in Main.ts that
accepts string | string[]).

130-136: Redundant map - AD_TYPES already has the right shape.

AD_TYPES already contains objects with only type and selectorId properties. The map creates identical copies with no transformation. Pass the array directly.

♻️ Simplify by passing AD_TYPES directly
       window.ramp.que.push(() => {
         try {
-          window.ramp.spaAddAds(
-            AD_TYPES.map(({ type, selectorId }) => ({ type, selectorId })),
-          );
+          window.ramp.spaAddAds(AD_TYPES);
           console.log(
             "In-game bottom-left ads loaded:",
             AD_TYPES.map((a) => a.type),
           );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/graphics/layers/InGamePromo.ts` around lines 130 - 136, In
InGamePromo, the call to window.ramp.spaAddAds unnecessarily maps AD_TYPES into
identical objects; remove the redundant map and pass AD_TYPES directly to
window.ramp.spaAddAds. Locate the call in the InGamePromo file (look for
window.ramp.spaAddAds and AD_TYPES) and change the argument from AD_TYPES.map(({
type, selectorId }) => ({ type, selectorId })) to AD_TYPES while leaving the
console.log that prints AD_TYPES.map(a => a.type) intact if you still want the
type list.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/client/graphics/layers/InGamePromo.ts`:
- Around line 162-167: AD_TYPES is being iterated to call
window.ramp.destroyUnits(type) multiple times; instead call
window.ramp.destroyUnits with an array of types in one call. Replace the loop
over AD_TYPES with a single call passing AD_TYPES.map(item => item.type) (or
otherwise extract the string[] of types) to window.ramp.destroyUnits, keeping
the same try/catch and console logging; reference window.ramp.destroyUnits and
AD_TYPES (and the type signature in Main.ts that accepts string | string[]).
- Around line 130-136: In InGamePromo, the call to window.ramp.spaAddAds
unnecessarily maps AD_TYPES into identical objects; remove the redundant map and
pass AD_TYPES directly to window.ramp.spaAddAds. Locate the call in the
InGamePromo file (look for window.ramp.spaAddAds and AD_TYPES) and change the
argument from AD_TYPES.map(({ type, selectorId }) => ({ type, selectorId })) to
AD_TYPES while leaving the console.log that prints AD_TYPES.map(a => a.type)
intact if you still want the type list.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c2b8dab5-ed2d-4865-8fe3-84e07256e006

📥 Commits

Reviewing files that changed from the base of the PR and between 10d02de and a713fc1.

📒 Files selected for processing (1)
  • src/client/graphics/layers/InGamePromo.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Dockerfile`:
- Around line 79-80: Replace the hard-coded "100h" timeout in the conditional
that checks DOMAIN and SUBDOMAIN with a configurable PREVIEW_TTL environment
variable: read PREVIEW_TTL (defaulting to "100h" via shell parameter expansion,
e.g. ${PREVIEW_TTL:-100h}) and if PREVIEW_TTL is "0" start supervisord directly
(exec /usr/bin/supervisord ...) otherwise run exec timeout "${PREVIEW_TTL}"
/usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf; keep the DOMAIN
and SUBDOMAIN checks intact so only preview containers use PREVIEW_TTL.

In `@src/client/graphics/layers/InGamePromo.ts`:
- Around line 143-167: hideAd currently only tears down bottom_rail and
left-side units but never removes the corner_ad_video added by
loadCornerAdVideo, so update hideAd to also remove/destroy that unit: when
tearing down, check for window.ramp and push a task to window.ramp.que that
calls the appropriate teardown API (e.g., window.ramp.removeUnits([{ type:
"corner_ad_video" }]) or the corresponding destroy/remove method provided by
RAMP), and wrap it in try/catch to log errors; apply the same removal logic to
the other similar load block (the second corner_ad_video addition) so all corner
videos are properly removed when hiding the layer.
- Around line 17-18: Reset the per-match ad flags inside the InGamePromo.init()
method so each match starts fresh: specifically set bottomRailDestroyed = false
and cornerAdShown = false (and shouldShow if intended) at the beginning of
init(). Locate the InGamePromo class fields (bottomRailDestroyed, cornerAdShown)
and update the init() method to explicitly reinitialize these flags to false to
ensure spawn-phase teardown and post-spawn ad loading run for each reused
instance.

In `@src/client/HomepagePromos.ts`:
- Around line 62-80: The loadBottomRail method currently queues the bottom_rail
ad regardless of the global ad gate; update loadBottomRail to first check
window.adsEnabled (treat undefined as false) and return early without setting
this.bottomRailActive or calling window.ramp.que.push if ads are disabled,
mirroring the gating logic used by onUserMeResponse() and
InGamePromo.showBottomRail(); keep the existing ramp existence checks and error
handling but ensure this.bottomRailActive is only set when ads are enabled and
the ad is actually queued.
- Around line 51-60: close() currently turns off bottomRailActive and adLoaded
but does not unset the visibility gate used by show(), so the fixed gutter
wrappers controlled by isVisible remain mounted and block clicks; update the
close() method to also set the visibility flag (flip whatever isVisible/state
variable the show() method uses) to false when destroying ads (e.g., set
isVisible = false or call the same visibility toggle used by show()), ensuring
handleJoinLobby() calling homepage-promos.close() will fully hide the promo
shell and remove the pointer-active side containers.
🪄 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: 820b3a1f-3f30-40aa-94c4-57ce5e3a1524

📥 Commits

Reviewing files that changed from the base of the PR and between 7e9074f and 4d46d48.

📒 Files selected for processing (6)
  • Dockerfile
  • index.html
  • src/client/HomepagePromos.ts
  • src/client/Main.ts
  • src/client/graphics/layers/InGamePromo.ts
  • src/core/configuration/DevConfig.ts
💤 Files with no reviewable changes (1)
  • index.html
✅ Files skipped from review due to trivial changes (1)
  • src/core/configuration/DevConfig.ts

Comment on lines 79 to +80
if [ "$DOMAIN" = openfront.dev ] && [ "$SUBDOMAIN" != main ]; then
exec timeout 18h /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
exec timeout 100h /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Find where DOMAIN/SUBDOMAIN and timeout are configured =="
rg -n -C2 'openfront\.dev|DOMAIN|SUBDOMAIN|timeout [0-9]+h|start\.sh|ENTRYPOINT'

echo
echo "== Find runtime restart/health settings =="
rg -n -C2 'restartPolicy|restart:|healthcheck|livenessProbe|readinessProbe|terminationGracePeriodSeconds|backoffLimit|ttlSecondsAfterFinished'

echo
echo "== List likely deployment/config files for manual follow-up =="
fd -i 'dockerfile|compose|deployment|kustomization|helm|values|procfile|\.ya?ml$|\.tf$'

Repository: openfrontio/OpenFrontIO

Length of output: 8110


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check restart policy assignment in update.sh ==="
sed -n '54,65p' update.sh

echo
echo "=== Search for any existing TTL, timeout, or duration environment variables ==="
rg -n 'TTL|TIMEOUT|DURATION|_TIME' --type sh --type dockerfile

echo
echo "=== Check if supervisord behavior is documented or tested ==="
rg -n 'supervisord' -A 3 -B 1 --type dockerfile

echo
echo "=== Check example.env to understand intended configuration ==="
cat example.env

Repository: openfrontio/OpenFrontIO

Length of output: 522


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Verify full restart policy context in update.sh ==="
sed -n '50,75p' update.sh

echo
echo "=== Search for TTL, timeout, duration patterns across all files ==="
rg -i 'TTL|TIMEOUT|DURATION|_TIME' -l

echo
echo "=== Check what supervisord manages in conf.d ==="
rg 'supervisord' -A 5 -B 1 

echo
echo "=== Check if any environment-based configuration already exists ==="
rg '\$\{[A-Z_]+:-' | head -20

Repository: openfrontio/OpenFrontIO

Length of output: 14030


Make preview TTL configurable instead of hard-coding 100h.

At line 80, preview containers (non-main openfront.dev) force shutdown after 100 hours even if healthy. Combined with RESTART=no in update.sh (line 54–60), this terminates the container and keeps it stopped—interrupting active QA sessions and tests.

A simple environment variable lets you tune or disable this timeout per deployment without rebuilding the image.

Suggested change
 if [ "$DOMAIN" = openfront.dev ] && [ "$SUBDOMAIN" != main ]; then
-    exec timeout 100h /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
+    PREVIEW_TTL="${PREVIEW_TTL:-100h}"
+    exec timeout "$PREVIEW_TTL" /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
 else
     exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
 fi

Pass PREVIEW_TTL=0 to disable timeout, or any duration like PREVIEW_TTL=200h.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if [ "$DOMAIN" = openfront.dev ] && [ "$SUBDOMAIN" != main ]; then
exec timeout 18h /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
exec timeout 100h /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
if [ "$DOMAIN" = openfront.dev ] && [ "$SUBDOMAIN" != main ]; then
PREVIEW_TTL="${PREVIEW_TTL:-100h}"
exec timeout "$PREVIEW_TTL" /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` around lines 79 - 80, Replace the hard-coded "100h" timeout in
the conditional that checks DOMAIN and SUBDOMAIN with a configurable PREVIEW_TTL
environment variable: read PREVIEW_TTL (defaulting to "100h" via shell parameter
expansion, e.g. ${PREVIEW_TTL:-100h}) and if PREVIEW_TTL is "0" start
supervisord directly (exec /usr/bin/supervisord ...) otherwise run exec timeout
"${PREVIEW_TTL}" /usr/bin/supervisord -c
/etc/supervisor/conf.d/supervisord.conf; keep the DOMAIN and SUBDOMAIN checks
intact so only preview containers use PREVIEW_TTL.

Comment on lines 17 to +18
private shouldShow: boolean = false;
private bottomRailActive: boolean = false;
private bottomRailDestroyed: boolean = 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.

⚠️ Potential issue | 🟠 Major

Reset the per-match ad flags in init().

bottomRailDestroyed and cornerAdShown are only initialized once. After one completed game they stay true, so a reused in-game-promo instance will skip the next match's spawn-phase teardown and post-spawn ad load.

Proposed fix
  init() {
+    this.shouldShow = false;
+    this.bottomRailDestroyed = false;
+    this.cornerAdShown = false;
     this.showBottomRail();
  }

Also applies to: 25-39

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

In `@src/client/graphics/layers/InGamePromo.ts` around lines 17 - 18, Reset the
per-match ad flags inside the InGamePromo.init() method so each match starts
fresh: specifically set bottomRailDestroyed = false and cornerAdShown = false
(and shouldShow if intended) at the beginning of init(). Locate the InGamePromo
class fields (bottomRailDestroyed, cornerAdShown) and update the init() method
to explicitly reinitialize these flags to false to ensure spawn-phase teardown
and post-spawn ad loading run for each reused instance.

Comment on lines 143 to 167
private loadCornerAdVideo(): void {
if (window.innerWidth < 1280) return;
if (!window.ramp) {
console.warn("Playwire RAMP not available for corner_ad_video");
return;
}
try {
window.ramp.que.push(() => {
try {
window.ramp
.addUnits([{ type: "corner_ad_video" }])
.then(() => {
window.ramp.displayUnits();
console.log("In-game corner_ad_video loaded");
})
.catch((e: unknown) => {
console.error("Failed to display in-game corner_ad_video:", e);
});
} catch (e) {
console.error("Failed to add in-game corner_ad_video:", e);
}
});
} catch (error) {
console.error("Failed to load in-game corner_ad_video:", error);
}
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.

⚠️ Potential issue | 🟠 Major

Destroy corner_ad_video when the layer hides.

The new video unit is added in loadCornerAdVideo(), but hideAd() only tears down bottom_rail and the left-side units. That can leave the corner video alive over the next screen or next match.

Proposed fix
  public hideAd(): void {
     this.destroyBottomRail();
+    this.cornerAdShown = false;
 
     if (crazyGamesSDK.isOnCrazyGames()) {
       crazyGamesSDK.clearBottomLeftAd();
       this.shouldShow = false;
       this.requestUpdate();
@@
     try {
       for (const { type } of AD_TYPES) {
         window.ramp.destroyUnits(type);
       }
+      window.ramp.destroyUnits("corner_ad_video");
       console.log("successfully destroyed in-game bottom-left ads");
     } catch (e) {
       console.error("error destroying in-game ads:", e);
     }

Also applies to: 170-193

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

In `@src/client/graphics/layers/InGamePromo.ts` around lines 143 - 167, hideAd
currently only tears down bottom_rail and left-side units but never removes the
corner_ad_video added by loadCornerAdVideo, so update hideAd to also
remove/destroy that unit: when tearing down, check for window.ramp and push a
task to window.ramp.que that calls the appropriate teardown API (e.g.,
window.ramp.removeUnits([{ type: "corner_ad_video" }]) or the corresponding
destroy/remove method provided by RAMP), and wrap it in try/catch to log errors;
apply the same removal logic to the other similar load block (the second
corner_ad_video addition) so all corner videos are properly removed when hiding
the layer.

Comment on lines 51 to +60
public close(): void {
this.bottomRailActive = false;
this.adLoaded = false;
try {
window.ramp.spaAds({ ads: [], countPageview: false });
console.log("successfully destroyed all homepage ads");
} catch (e) {
console.error("error destroying homepage ads", e);
}
}
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.

⚠️ Potential issue | 🟠 Major

Hide the promo shell inside close().

show() gates the fixed gutter wrappers behind isVisible, but close() never flips that flag back. handleJoinLobby() still calls homepage-promos.close(), so the empty, pointer-active side containers stay mounted after the ads are destroyed and can keep blocking clicks near the page edges.

Proposed fix
  public close(): void {
+    this.isVisible = false;
     this.bottomRailActive = false;
     this.adLoaded = false;
+    this.cornerAdLoaded = false;
+    this.requestUpdate();
     try {
       window.ramp.spaAds({ ads: [], countPageview: false });
       console.log("successfully destroyed all homepage ads");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public close(): void {
this.bottomRailActive = false;
this.adLoaded = false;
try {
window.ramp.spaAds({ ads: [], countPageview: false });
console.log("successfully destroyed all homepage ads");
} catch (e) {
console.error("error destroying homepage ads", e);
}
}
public close(): void {
this.isVisible = false;
this.bottomRailActive = false;
this.adLoaded = false;
this.cornerAdLoaded = false;
this.requestUpdate();
try {
window.ramp.spaAds({ ads: [], countPageview: false });
console.log("successfully destroyed all homepage ads");
} catch (e) {
console.error("error destroying homepage ads", e);
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/HomepagePromos.ts` around lines 51 - 60, close() currently turns
off bottomRailActive and adLoaded but does not unset the visibility gate used by
show(), so the fixed gutter wrappers controlled by isVisible remain mounted and
block clicks; update the close() method to also set the visibility flag (flip
whatever isVisible/state variable the show() method uses) to false when
destroying ads (e.g., set isVisible = false or call the same visibility toggle
used by show()), ensuring handleJoinLobby() calling homepage-promos.close() will
fully hide the promo shell and remove the pointer-active side containers.

Comment on lines +62 to +80
public loadBottomRail(): void {
if (!window.ramp) {
console.warn("Playwire RAMP not available for bottom_rail ad");
return;
}

this.bottomRailActive = true;
try {
window.ramp.que.push(() => {
try {
window.ramp.spaAddAds([{ type: "bottom_rail" }]);
console.log("Bottom rail ad loaded");
} catch (e) {
console.error("Failed to add bottom_rail ad:", e);
}
});
} catch (error) {
console.error("Failed to load bottom_rail ad:", error);
}
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.

⚠️ Potential issue | 🟠 Major

Respect window.adsEnabled before queuing bottom_rail.

This path skips the same ad gate used by onUserMeResponse() and InGamePromo.showBottomRail(). Right now a lobby join can still queue bottom_rail for sessions that are supposed to stay ad-free.

Proposed fix
  public loadBottomRail(): void {
+    if (!window.adsEnabled) {
+      return;
+    }
+
     if (!window.ramp) {
       console.warn("Playwire RAMP not available for bottom_rail ad");
       return;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public loadBottomRail(): void {
if (!window.ramp) {
console.warn("Playwire RAMP not available for bottom_rail ad");
return;
}
this.bottomRailActive = true;
try {
window.ramp.que.push(() => {
try {
window.ramp.spaAddAds([{ type: "bottom_rail" }]);
console.log("Bottom rail ad loaded");
} catch (e) {
console.error("Failed to add bottom_rail ad:", e);
}
});
} catch (error) {
console.error("Failed to load bottom_rail ad:", error);
}
public loadBottomRail(): void {
if (!window.adsEnabled) {
return;
}
if (!window.ramp) {
console.warn("Playwire RAMP not available for bottom_rail ad");
return;
}
this.bottomRailActive = true;
try {
window.ramp.que.push(() => {
try {
window.ramp.spaAddAds([{ type: "bottom_rail" }]);
console.log("Bottom rail ad loaded");
} catch (e) {
console.error("Failed to add bottom_rail ad:", e);
}
});
} catch (error) {
console.error("Failed to load bottom_rail ad:", error);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/HomepagePromos.ts` around lines 62 - 80, The loadBottomRail method
currently queues the bottom_rail ad regardless of the global ad gate; update
loadBottomRail to first check window.adsEnabled (treat undefined as false) and
return early without setting this.bottomRailActive or calling
window.ramp.que.push if ads are disabled, mirroring the gating logic used by
onUserMeResponse() and InGamePromo.showBottomRail(); keep the existing ramp
existence checks and error handling but ensure this.bottomRailActive is only set
when ads are enabled and the ad is actually queued.

@github-project-automation github-project-automation bot moved this from Final Review to Development in OpenFront Release Management Apr 9, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
src/client/HomepagePromos.ts (2)

63-70: ⚠️ Potential issue | 🟠 Major

Respect window.adsEnabled before queuing bottom_rail.

Right now this queues ads even for sessions that should be ad-free.

Proposed fix
   public loadBottomRail(): void {
+    if (!window.adsEnabled || this.bottomRailActive) {
+      return;
+    }
+
     if (!window.ramp) {
       console.warn("Playwire RAMP not available for bottom_rail ad");
       return;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/HomepagePromos.ts` around lines 63 - 70, The loadBottomRail method
currently queues the bottom_rail ad regardless of ad-free sessions; update
loadBottomRail to first check window.adsEnabled (and treat undefined as true
only if your app policy allows) and return early without setting
this.bottomRailActive or calling window.ramp.queue if ads are disabled;
specifically, add a guard at the top of loadBottomRail that verifies
window.adsEnabled before the existing window.ramp and bottomRailActive logic and
only proceed to queue "bottom_rail" when ads are enabled.

51-57: ⚠️ Potential issue | 🟠 Major

close() should also hide the promo shell state.

show() uses isVisible to render the wrappers, but close() never resets it. Empty gutter wrappers can remain mounted and block clicks near edges.

Proposed fix
   public close(): void {
+    this.isVisible = false;
     this.adLoaded = false;
+    this.cornerAdLoaded = false;
+    this.requestUpdate();
     try {
       // Only destroy gutter ads; bottom_rail persists into spawn phase.
       window.ramp.destroyUnits(this.leftAdType);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/HomepagePromos.ts` around lines 51 - 57, The close() method
currently only flips adLoaded and destroys gutter units but never resets the
promo shell render flag; update close() (alongside its existing adLoaded=false
and destroyUnits calls for leftAdType/rightAdType) to also set this.isVisible =
false so the wrappers rendered by show() are unmounted and stop blocking clicks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/client/HomepagePromos.ts`:
- Around line 90-93: The try/catch around the async call
window.ramp.destroyUnits("bottom_rail") will not catch Promise rejections;
update the call to handle its Promise (either await the call by making the
enclosing function async, or attach a .catch() handler) and log the error via
process/logger/console inside the rejection handler; do the same fix for the
analogous call in InGamePromo (where destroyUnits is called) so both Promise
rejections are properly handled and errors are logged.

In `@src/client/Main.ts`:
- Around line 872-875: The bottom-rail cleanup (calling
HomepagePromos.destroyBottomRail on the homepagePromos instance) must run even
if this.lobbyHandle is null; move or copy the block that queries
document.querySelector("homepage-promos") and calls
homepagePromos.destroyBottomRail() to execute before the early-return/null-guard
around this.lobbyHandle in the leave-lobby handler (or ensure the handler always
invokes that cleanup regardless of this.lobbyHandle). Locate the code around the
leave-lobby handler where this.lobbyHandle is checked and adjust so
HomepagePromos.destroyBottomRail is invoked unconditionally prior to returning.

---

Duplicate comments:
In `@src/client/HomepagePromos.ts`:
- Around line 63-70: The loadBottomRail method currently queues the bottom_rail
ad regardless of ad-free sessions; update loadBottomRail to first check
window.adsEnabled (and treat undefined as true only if your app policy allows)
and return early without setting this.bottomRailActive or calling
window.ramp.queue if ads are disabled; specifically, add a guard at the top of
loadBottomRail that verifies window.adsEnabled before the existing window.ramp
and bottomRailActive logic and only proceed to queue "bottom_rail" when ads are
enabled.
- Around line 51-57: The close() method currently only flips adLoaded and
destroys gutter units but never resets the promo shell render flag; update
close() (alongside its existing adLoaded=false and destroyUnits calls for
leftAdType/rightAdType) to also set this.isVisible = false so the wrappers
rendered by show() are unmounted and stop blocking clicks.
🪄 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: 454f7c2c-b6f5-4f36-b3d9-0dbe7697631b

📥 Commits

Reviewing files that changed from the base of the PR and between 4d46d48 and 187b9f8.

📒 Files selected for processing (6)
  • Dockerfile
  • index.html
  • src/client/HomepagePromos.ts
  • src/client/Main.ts
  • src/client/graphics/layers/InGamePromo.ts
  • src/core/configuration/DevConfig.ts
💤 Files with no reviewable changes (1)
  • index.html
✅ Files skipped from review due to trivial changes (2)
  • Dockerfile
  • src/core/configuration/DevConfig.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/client/graphics/layers/InGamePromo.ts

Comment on lines +90 to +93
try {
window.ramp.destroyUnits("bottom_rail");
console.log("Bottom rail ad destroyed");
} catch (e) {
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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify Promise-returning signature and current call handling.
rg -n -C2 'destroyUnits\s*:\s*\(adType:\s*string\s*\|\s*string\[\]\)\s*=>\s*Promise<void>' src/client/Main.ts
rg -n -C3 'destroyUnits\("bottom_rail"\)' src/client/HomepagePromos.ts src/client/graphics/layers/InGamePromo.ts

Repository: openfrontio/OpenFrontIO

Length of output: 1284


🏁 Script executed:

#!/bin/bash
# Check the function context around the destroyUnits calls
echo "=== HomepagePromos.ts context ==="
sed -n '80,100p' src/client/HomepagePromos.ts

echo -e "\n=== InGamePromo.ts context ==="
sed -n '35,50p' src/client/graphics/layers/InGamePromo.ts

# Check if there are any .catch() chains after these calls
echo -e "\n=== Check for .catch chains ==="
rg -n 'destroyUnits.*\.catch|\.then.*catch' src/client/

Repository: openfrontio/OpenFrontIO

Length of output: 1123


Handle async destroyUnits failures with .catch() or await.

window.ramp.destroyUnits("bottom_rail") returns a Promise. A sync try/catch will not catch Promise rejection, so failures become unhandled. Either add .catch() to handle the rejection inline, or make the parent function async and await the call.

This issue appears in:

  • src/client/HomepagePromos.ts:91
  • src/client/graphics/layers/InGamePromo.ts:45
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/HomepagePromos.ts` around lines 90 - 93, The try/catch around the
async call window.ramp.destroyUnits("bottom_rail") will not catch Promise
rejections; update the call to handle its Promise (either await the call by
making the enclosing function async, or attach a .catch() handler) and log the
error via process/logger/console inside the rejection handler; do the same fix
for the analogous call in InGamePromo (where destroyUnits is called) so both
Promise rejections are properly handled and errors are logged.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
src/client/HomepagePromos.ts (3)

53-56: ⚠️ Potential issue | 🔴 Critical

Handle destroyUnits() as async.

The calls here are still wrapped in sync try/catch. If destroyUnits() rejects, the failure is unhandled while adLoaded and bottomRailActive are already set to false, so the component can think the slots are gone when teardown actually failed.

#!/bin/bash
echo "=== destroyUnits type ==="
rg -n -C2 'destroyUnits\s*:\s*\(adType:\s*string\s*\|\s*string\[\]\)\s*=>\s*Promise<void>' src

echo
echo "=== HomepagePromos destroyUnits call sites ==="
rg -n -C2 'destroyUnits\((this\.leftAdType|this\.rightAdType|"bottom_rail")\)' src/client/HomepagePromos.ts

Expected result: the first search should show the Promise-returning signature, and the second should show these un-awaited call sites. If that matches, these paths need await or .catch() before logging success.

Also applies to: 90-93

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

In `@src/client/HomepagePromos.ts` around lines 53 - 56, The calls to
window.ramp.destroyUnits in HomepagePromos are async but currently invoked
without awaiting or handling rejections, so failures can be unobserved while
state (adLoaded, bottomRailActive) is flipped; update the teardown in the
HomepagePromos method that calls window.ramp.destroyUnits(this.leftAdType) and
window.ramp.destroyUnits(this.rightAdType) (and the similar calls at lines
~90-93) to await each Promise or attach .catch() handlers and only set
adLoaded/bottomRailActive to false after successful completion (or revert state
on error), ensuring errors are logged via processLogger or console.error and not
left unhandled.

63-70: ⚠️ Potential issue | 🟠 Major

Respect window.adsEnabled before loading bottom_rail.

This path still skips the same ad gate used in onUserMeResponse(). That means a lobby join can queue bottom_rail for ad-free sessions, and it sets bottomRailActive even when ads should stay off.

Suggested fix
  public loadBottomRail(): void {
+    if (!window.adsEnabled) {
+      return;
+    }
+
     if (!window.ramp) {
       console.warn("Playwire RAMP not available for bottom_rail ad");
       return;
     }
 
     this.bottomRailActive = true;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/HomepagePromos.ts` around lines 63 - 70, loadBottomRail currently
activates and queues bottom_rail without checking the global ad gate; before
touching window.ramp or setting this.bottomRailActive, mirror the same ad gating
used in onUserMeResponse by checking window.adsEnabled (or the project's
canonical ads flag) and return early if ads are disabled, ensuring you do not
set this.bottomRailActive or call ramp.load for "bottom_rail" when
window.adsEnabled is false.

51-52: ⚠️ Potential issue | 🟠 Major

Hide the promo shell in close().

Line 51 only resets adLoaded. show() mounts the fixed gutter wrappers behind isVisible, but close() never clears that flag or triggers a re-render, so the empty wrappers can stay on screen and block clicks after join starts.

Suggested fix
  public close(): void {
+    this.isVisible = false;
     this.adLoaded = false;
+    this.requestUpdate();
     try {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/HomepagePromos.ts` around lines 51 - 52, The close() method
currently only clears adLoaded and leaves the mounted fixed gutter wrappers
visible; update close() to also clear the visibility flag and trigger the same
re-render/unmount path that show() uses so the wrappers are removed — i.e., set
this.isVisible = false and then invoke the component's UI-update routine (for
React components use this.setState({isVisible: false}) or this.forceUpdate(),
otherwise call the existing DOM update/unmount method used by show()).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/client/HomepagePromos.ts`:
- Around line 51-56: In the HomepagePromos.close() method, add teardown for the
corner video by calling window.ramp.destroyUnits("corner_ad_video") alongside
the existing gutter destroys and reset the state by setting this.cornerAdLoaded
= false; ensure the destroy call is inside the existing try block (with the
other window.ramp.destroyUnits calls) so the corner unit is removed and the
cornerAdLoaded flag is cleared when close() is invoked.

---

Duplicate comments:
In `@src/client/HomepagePromos.ts`:
- Around line 53-56: The calls to window.ramp.destroyUnits in HomepagePromos are
async but currently invoked without awaiting or handling rejections, so failures
can be unobserved while state (adLoaded, bottomRailActive) is flipped; update
the teardown in the HomepagePromos method that calls
window.ramp.destroyUnits(this.leftAdType) and
window.ramp.destroyUnits(this.rightAdType) (and the similar calls at lines
~90-93) to await each Promise or attach .catch() handlers and only set
adLoaded/bottomRailActive to false after successful completion (or revert state
on error), ensuring errors are logged via processLogger or console.error and not
left unhandled.
- Around line 63-70: loadBottomRail currently activates and queues bottom_rail
without checking the global ad gate; before touching window.ramp or setting
this.bottomRailActive, mirror the same ad gating used in onUserMeResponse by
checking window.adsEnabled (or the project's canonical ads flag) and return
early if ads are disabled, ensuring you do not set this.bottomRailActive or call
ramp.load for "bottom_rail" when window.adsEnabled is false.
- Around line 51-52: The close() method currently only clears adLoaded and
leaves the mounted fixed gutter wrappers visible; update close() to also clear
the visibility flag and trigger the same re-render/unmount path that show() uses
so the wrappers are removed — i.e., set this.isVisible = false and then invoke
the component's UI-update routine (for React components use
this.setState({isVisible: false}) or this.forceUpdate(), otherwise call the
existing DOM update/unmount method used by show()).
🪄 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: a56c36d9-3d89-46f7-932c-1bbf575e9255

📥 Commits

Reviewing files that changed from the base of the PR and between 187b9f8 and b66824a.

📒 Files selected for processing (6)
  • Dockerfile
  • index.html
  • src/client/HomepagePromos.ts
  • src/client/Main.ts
  • src/client/graphics/layers/InGamePromo.ts
  • src/core/configuration/DevConfig.ts
💤 Files with no reviewable changes (1)
  • index.html
✅ Files skipped from review due to trivial changes (3)
  • Dockerfile
  • src/core/configuration/DevConfig.ts
  • src/client/Main.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/client/graphics/layers/InGamePromo.ts

Comment on lines 51 to 56
public close(): void {
this.adLoaded = false;
try {
// Keep corner video ad alive.
// Only destroy gutter ads; bottom_rail persists into spawn phase.
window.ramp.destroyUnits(this.leftAdType);
window.ramp.destroyUnits(this.rightAdType);
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== corner_ad_video lifecycle in HomepagePromos ==="
rg -n -C2 'corner_ad_video|cornerAdLoaded|loadCornerAdVideo|public close\(' src/client/HomepagePromos.ts

echo
echo "=== close() call during join flow ==="
sed -n '773,789p' src/client/Main.ts

echo
echo "=== repo-wide teardown references for corner_ad_video ==="
rg -n -C2 'destroyUnits\("corner_ad_video"\)|destroyUnits\(.*corner_ad_video|removeUnits\(.*corner_ad_video' src

Repository: openfrontio/OpenFrontIO

Length of output: 2098


Destroy corner_ad_video and reset its flag when close() is called.

loadCornerAdVideo() adds the corner video unit via window.ramp.addUnits() and sets cornerAdLoaded = true, but close() only destroys the gutter ads and never resets cornerAdLoaded or removes the corner unit. Since Main.ts calls close() when the game starts, the corner video remains active with no teardown path.

Add cleanup logic to close():

  • Call window.ramp.destroyUnits("corner_ad_video")
  • Reset this.cornerAdLoaded = false
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/HomepagePromos.ts` around lines 51 - 56, In the
HomepagePromos.close() method, add teardown for the corner video by calling
window.ramp.destroyUnits("corner_ad_video") alongside the existing gutter
destroys and reset the state by setting this.cornerAdLoaded = false; ensure the
destroy call is inside the existing try block (with the other
window.ramp.destroyUnits calls) so the corner unit is removed and the
cornerAdLoaded flag is cleared when close() is invoked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Development

Development

Successfully merging this pull request may close these issues.

1 participant