Skip to content

Conversation

@evanpelle
Copy link
Collaborator

@evanpelle evanpelle commented Jan 12, 2026

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

Description:

Before, certain vars from the server were either hardcoded or retrieved from the /env endpoint or /commit.txt. Instead we can simplify this by just inject env variables into index.html so it's available to the client immediately.

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:

evan

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 12, 2026

Walkthrough

Refactors configuration management to unify server config retrieval through getServerConfig(), remove the /api/env endpoint, move GameEnv from ServerConfig to a direct parameter in Config, and inject server config data into the client via window.SERVER_CONFIG instead of exposing process environment variables.

Changes

Cohort / File(s) Summary
Configuration System Core
src/core/configuration/Config.ts, src/core/configuration/ConfigLoader.ts, src/core/configuration/Env.ts
Enum value renamed (PreprodStaging); Config interface now exposes env(): GameEnv instead of serverConfig(): ServerConfig. getConfig() now accepts GameEnv as a direct parameter. New getServerConfig() replaces separate client/server retrieval functions. Env enhanced to read from window.SERVER_CONFIG with fallback to environment variables.
Config Implementations
src/core/configuration/DefaultConfig.ts, src/core/configuration/DevConfig.ts, src/core/configuration/PreprodConfig.ts
Constructor signatures updated to accept GameEnv instead of ServerConfig as a dependency. Methods updated: env() added, serverConfig() removed.
Worker & Message Communication
src/core/worker/WorkerClient.ts, src/core/worker/Worker.worker.ts, src/core/worker/WorkerMessages.ts, src/core/GameRunner.ts
WorkerClient constructor and worker messages augmented with env: GameEnv parameter. createGameRunner() signature updated to accept env as second parameter. Worker init path passes environment to createGameRunner().
Client Usage Updates
src/client/ClientGameRunner.ts, src/client/HostLobbyModal.ts, src/client/JoinPrivateLobbyModal.ts, src/client/Main.ts, src/client/Matchmaking.ts, src/client/graphics/layers/MultiTabModal.ts
getServerConfigFromClient() calls replaced with getServerConfig(). Config dependency injection simplified. Git commit retrieval in JoinPrivateLobbyModal migrated to use Env.GIT_COMMIT. Environment checks updated to use config.env() instead of config.serverConfig().env().
Server Usage Updates
src/server/Archive.ts, src/server/Logger.ts, src/server/MapPlaylist.ts, src/server/Master.ts, src/server/OtelResource.ts, src/server/Server.ts, src/server/Worker.ts, src/server/WorkerMetrics.ts
getServerConfigFromServer() calls replaced with getServerConfig(). /api/env endpoint removed from Master.ts.
Build & Runtime Configuration
vite.config.ts, index.html, .vscode/launch.json, .vscode/settings.json, package.json, nginx.conf
HTML injection payload added for serverConfig (gameEnv, numWorkers, gitCommit). /api/env nginx location block removed. Environment variable changed from "dev" to "Dev". HTML script validation disabled.
Test Utilities
tests/core/pathfinding/_fixtures.ts, tests/pathfinding/utils.ts, tests/util/Setup.ts
TestServerConfig imports and instantiations removed. TestConfig constructor calls updated to pass GameEnv.Dev instead of server config instance.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

Release Blocker

Suggested reviewers

  • drillskibo
  • scottanderson

Poem

🔧 The config has shifted, from scattered to clean,
Env flows like water, no server in between,
Workers now know their place in the game,
Window spreads secrets—but they're all the same! 📦✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main changes: injecting server config into index.html and removing the /api/env endpoint, which are central to this refactoring PR.
Description check ✅ Passed The description adequately explains the PR objective: injecting environment variables into index.html instead of hardcoding or fetching from endpoints.

✏️ 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.

@evanpelle evanpelle force-pushed the evan-fix-num-workers branch from c4a6aea to 37c5410 Compare January 12, 2026 23:24
Copy link
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: 3

🤖 Fix all issues with AI agents
In @src/core/configuration/ConfigLoader.ts:
- Around line 23-25: The default branch throws an error referencing the global
Env.GAME_ENV instead of the function parameter gameEnv; update the thrown Error
in the default case to interpolate the local parameter (gameEnv) so the message
correctly reflects the input value — look for the switch's default branch in
ConfigLoader.ts and replace Env.GAME_ENV with gameEnv in the Error construction.

In @src/core/configuration/Env.ts:
- Around line 105-114: Env.GIT_COMMIT can be undefined, so in
JoinPrivateLobbyModal where myGitCommit is compared to "DEV" you must normalize
the value before comparing (e.g., set myGitCommit = Env.GIT_COMMIT ?? "DEV" or
otherwise handle undefined explicitly) to avoid treating undefined as a non-DEV
commit; mirror the approach used in DefaultConfig (Env.GIT_COMMIT ?? "") and
update the comparison logic that currently checks myGitCommit !== "DEV" to use
the normalized value.

In @vite.config.ts:
- Around line 59-63: serverConfig currently uses parseInt(env.NUM_WORKERS, 10)
(which can produce NaN) and directly uses env.GIT_COMMIT (bypassing the earlier
fallback); update the serverConfig construction so numWorkers uses a safe parse
with a fallback (e.g., Number.isFinite/Number and default to 2 or the non-prod
default when parseInt yields NaN) and reuse the already-computed gitCommit
fallback value instead of env.GIT_COMMIT, referencing the serverConfig object
and the numWorkers and gitCommit keys to locate where to change.
🧹 Nitpick comments (10)
.vscode/settings.json (1)

1-3: Verify this workspace setting is necessary and document why.

Disabling HTML script validation affects all HTML files in the workspace. If this was added to suppress false positives from the new window.SERVER_CONFIG injection approach, consider:

  1. Documenting the reason in a comment or README
  2. Whether a more targeted approach exists (e.g., inline directives for specific files)
  3. The trade-off: legitimate script errors in HTML won't be caught by VSCode

Can you confirm this setting is needed for the config injection changes? If so, please add a code comment or documentation explaining why, so future maintainers understand the reasoning.

src/server/Server.ts (1)

12-12: Consider storing or documenting the discarded return value.

The call to getServerConfig() returns a ServerConfig object that is discarded. If this is intentional for early validation and logging (to fail fast on misconfiguration), add a brief comment. Otherwise, consider storing it if needed downstream.

-  getServerConfig();
+  // Validate environment and log active configuration before forking workers
+  getServerConfig();
tests/core/pathfinding/_fixtures.ts (1)

3-3: Mixed import styles in the file need consistency.

This file uses both "src/core/... imports (line 3) and relative paths like "../../../src/core/..." (lines 5-10). Both work due to baseUrl: "." in tsconfig, but the inconsistency within the same file reduces readability. Consider standardizing on one style—either use relative paths for all imports or use the "src/..." pattern consistently.

src/client/JoinPrivateLobbyModal.ts (2)

515-516: getServerConfig() is synchronous - no need for await.

Looking at ConfigLoader.ts, getServerConfig() returns ServerConfig directly, not a Promise<ServerConfig>. The await is not needed here.

♻️ Suggested fix
 private async checkActiveLobby(lobbyId: string): Promise<boolean> {
-   const config = await getServerConfig();
+   const config = getServerConfig();
    const url = `/${config.workerPath(lobbyId)}/api/game/${lobbyId}/exists`;

598-603: Same here - getServerConfig() is synchronous.

This method can be simplified since getServerConfig() does not return a Promise.

♻️ Suggested fix
 private async pollPlayers() {
    const lobbyId = this.currentLobbyId;
    if (!lobbyId) return;
-   const config = await getServerConfig();
+   const config = getServerConfig();

    fetch(`/${config.workerPath(lobbyId)}/api/game/${lobbyId}`, {
src/client/Main.ts (3)

738-738: getServerConfig() returns synchronously - await is not needed.

The new getServerConfig() function returns ServerConfig directly. No Promise here.

♻️ Suggested fix
-   const config = await getServerConfig();
+   const config = getServerConfig();

895-904: Same pattern - unnecessary await on synchronous call.

♻️ Suggested fix
-   const config = await getServerConfig();
+   const config = getServerConfig();

958-958: Third occurrence of unnecessary await.

♻️ Suggested fix
-  const config = await getServerConfig();
+  const config = getServerConfig();
src/core/configuration/Env.ts (1)

59-85: Console logs on every access will spam the logs.

The GAME_ENV getter logs every time it is accessed. Since this is a getter, each call triggers a log. In a long session, this creates noise.

Consider caching the result or removing the logs after initial debugging.

♻️ Suggested fix - cache the value
+let cachedGameEnv: GameEnv | null = null;
+
 export const Env = {
   get GAME_ENV(): GameEnv {
+    if (cachedGameEnv !== null) {
+      return cachedGameEnv;
+    }
+
     // Check window.SERVER_CONFIG first (injected at build time)
     try {
       if (typeof window !== "undefined" && window.SERVER_CONFIG) {
         console.log("using server config from window");
-        return GameEnv[window.SERVER_CONFIG.gameEnv as keyof typeof GameEnv];
+        cachedGameEnv = GameEnv[window.SERVER_CONFIG.gameEnv as keyof typeof GameEnv];
+        return cachedGameEnv;
       }
     } catch {
       // Ignore errors accessing window
     }

     // Check MODE for Vite, GAME_ENV for Node
     try {
       if (
         typeof import.meta !== "undefined" &&
         import.meta.env &&
         import.meta.env.MODE
       ) {
         console.log("using server config from import.meta.env");
-        return GameEnv[import.meta.env.MODE as keyof typeof GameEnv];
+        cachedGameEnv = GameEnv[import.meta.env.MODE as keyof typeof GameEnv];
+        return cachedGameEnv;
       }
     } catch {
       // Ignore errors accessing import.meta
     }
     console.log("using server config from environment variable");
-    return GameEnv[getEnv("GAME_ENV") as keyof typeof GameEnv];
+    cachedGameEnv = GameEnv[getEnv("GAME_ENV") as keyof typeof GameEnv];
+    return cachedGameEnv;
   },
src/core/configuration/ConfigLoader.ts (1)

31-39: Logging on every call to getServerConfig() will create noise.

Similar to Env.GAME_ENV, these console.log statements fire on every call. Consider caching the server config or removing the logs.

♻️ Optional: cache the server config
+let cachedServerConfig: ServerConfig | null = null;
+
 export function getServerConfig(): ServerConfig {
+  if (cachedServerConfig !== null) {
+    return cachedServerConfig;
+  }
+
   const gameEnv = Env.GAME_ENV;
   switch (gameEnv) {
     case GameEnv.Dev:
       console.log("using dev server config");
-      return new DevServerConfig();
+      cachedServerConfig = new DevServerConfig();
+      return cachedServerConfig;
     case GameEnv.Preprod:
       console.log("using preprod server config");
-      return preprodConfig;
+      cachedServerConfig = preprodConfig;
+      return cachedServerConfig;
     case GameEnv.Prod:
       console.log("using prod server config");
-      return prodConfig;
+      cachedServerConfig = prodConfig;
+      return cachedServerConfig;
     default:
       throw Error(`unsupported server configuration: ${gameEnv}`);
   }
 }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a7714cd and c4a6aea.

📒 Files selected for processing (32)
  • .vscode/launch.json
  • .vscode/settings.json
  • index.html
  • nginx.conf
  • package.json
  • src/client/ClientGameRunner.ts
  • src/client/HostLobbyModal.ts
  • src/client/JoinPrivateLobbyModal.ts
  • src/client/Main.ts
  • src/client/Matchmaking.ts
  • src/client/graphics/layers/MultiTabModal.ts
  • src/core/GameRunner.ts
  • src/core/configuration/Config.ts
  • src/core/configuration/ConfigLoader.ts
  • src/core/configuration/DefaultConfig.ts
  • src/core/configuration/DevConfig.ts
  • src/core/configuration/Env.ts
  • src/core/worker/Worker.worker.ts
  • src/core/worker/WorkerClient.ts
  • src/core/worker/WorkerMessages.ts
  • src/server/Archive.ts
  • src/server/Logger.ts
  • src/server/MapPlaylist.ts
  • src/server/Master.ts
  • src/server/OtelResource.ts
  • src/server/Server.ts
  • src/server/Worker.ts
  • src/server/WorkerMetrics.ts
  • tests/core/pathfinding/_fixtures.ts
  • tests/pathfinding/utils.ts
  • tests/util/Setup.ts
  • vite.config.ts
💤 Files with no reviewable changes (1)
  • nginx.conf
🧰 Additional context used
🧠 Learnings (12)
📚 Learning: 2025-12-29T23:33:17.920Z
Learnt from: wraith4081
Repo: openfrontio/OpenFrontIO PR: 2735
File: index.html:390-391
Timestamp: 2025-12-29T23:33:17.920Z
Learning: In Tailwind CSS v4, update blur-related utilities in HTML templates. The mappings are: backdrop-blur-sm (v3) -> backdrop-blur-xs (v4); backdrop-blur (bare) -> backdrop-blur-sm; blur-sm -> blur-xs. Apply these changes to all HTML files (e.g., index.html and other templates) to reflect the v4 naming. Consider updating a project-wide search/replace or using a codemod to ensure consistency across the codebase.

Applied to files:

  • index.html
📚 Learning: 2026-01-12T21:37:01.156Z
Learnt from: FloPinguin
Repo: openfrontio/OpenFrontIO PR: 2874
File: src/server/MapLandTiles.ts:7-11
Timestamp: 2026-01-12T21:37:01.156Z
Learning: In this repository's OpenFrontIO deployment, inter-service HTTP calls to the master should target http://localhost:3000 (master at port 3000) as the canonical address. Apply this as the standard for all server-side TypeScript code that communicates with the master. Avoid hardcoding non-master URLs; centralize the master address (e.g., via config or env) when possible, and ensure internal service communication uses localhost:3000 in this architecture.

Applied to files:

  • src/core/worker/Worker.worker.ts
  • src/server/Logger.ts
  • src/client/JoinPrivateLobbyModal.ts
  • src/client/Matchmaking.ts
  • src/client/graphics/layers/MultiTabModal.ts
  • src/server/Archive.ts
  • src/server/WorkerMetrics.ts
  • src/core/configuration/Config.ts
  • src/server/Server.ts
  • src/core/GameRunner.ts
  • src/core/configuration/Env.ts
  • tests/core/pathfinding/_fixtures.ts
  • src/server/Worker.ts
  • src/core/worker/WorkerMessages.ts
  • src/server/MapPlaylist.ts
  • src/core/worker/WorkerClient.ts
  • src/server/OtelResource.ts
  • vite.config.ts
  • src/server/Master.ts
  • src/client/ClientGameRunner.ts
  • src/client/HostLobbyModal.ts
  • src/core/configuration/ConfigLoader.ts
  • src/core/configuration/DevConfig.ts
  • src/core/configuration/DefaultConfig.ts
  • src/client/Main.ts
  • tests/util/Setup.ts
  • tests/pathfinding/utils.ts
📚 Learning: 2025-06-09T02:20:43.637Z
Learnt from: VariableVince
Repo: openfrontio/OpenFrontIO PR: 1110
File: src/client/Main.ts:293-295
Timestamp: 2025-06-09T02:20:43.637Z
Learning: In src/client/Main.ts, during game start in the handleJoinLobby callback, UI elements are hidden using direct DOM manipulation with classList.add("hidden") for consistency. This includes modals, buttons, and error divs. The codebase follows this pattern rather than using component APIs for hiding elements during game transitions.

Applied to files:

  • src/client/JoinPrivateLobbyModal.ts
  • src/client/graphics/layers/MultiTabModal.ts
  • src/client/HostLobbyModal.ts
  • src/client/Main.ts
📚 Learning: 2026-01-02T18:11:15.132Z
Learnt from: ryanbarlow97
Repo: openfrontio/OpenFrontIO PR: 2740
File: src/client/HostLobbyModal.ts:821-821
Timestamp: 2026-01-02T18:11:15.132Z
Learning: In src/client/HostLobbyModal.ts, the `?s=xxxxx` URL suffix in lobby URLs is purely for cache-busting embed previews on platforms like Discord, WhatsApp, and x.com. The suffix value is ignored by the join logic (any value works), so regenerating it on config changes via `updateUrlWithSuffix()` doesn't break existing shared URLs - it only forces platforms to re-fetch updated preview metadata.

Applied to files:

  • src/client/JoinPrivateLobbyModal.ts
📚 Learning: 2025-12-26T22:21:21.904Z
Learnt from: FloPinguin
Repo: openfrontio/OpenFrontIO PR: 2689
File: src/client/PublicLobby.ts:245-245
Timestamp: 2025-12-26T22:21:21.904Z
Learning: In public lobbies with HumansVsNations mode in src/client/PublicLobby.ts, maxPlayers represents only human player slots (already halved in DefaultConfig.ts). The nation NPCs are added automatically server-side and don't count toward maxPlayers. Therefore, getTeamSize correctly returns maxPlayers directly for HumansVsNations to display the proper team size (e.g., maxPlayers=5 yields "5 Humans vs 5 Nations").

Applied to files:

  • src/client/JoinPrivateLobbyModal.ts
  • src/client/HostLobbyModal.ts
📚 Learning: 2025-08-12T00:31:50.144Z
Learnt from: scottanderson
Repo: openfrontio/OpenFrontIO PR: 1752
File: src/core/game/Game.ts:750-752
Timestamp: 2025-08-12T00:31:50.144Z
Learning: In the OpenFrontIO codebase, changes to the PlayerInteraction interface (like adding canDonateGold and canDonateTroops flags) do not require corresponding updates to src/core/Schemas.ts or server serialization code.

Applied to files:

  • src/client/Matchmaking.ts
  • src/core/configuration/DevConfig.ts
  • src/client/Main.ts
📚 Learning: 2025-08-09T05:03:18.745Z
Learnt from: scottanderson
Repo: openfrontio/OpenFrontIO PR: 1761
File: src/core/ApiSchemas.ts:2-2
Timestamp: 2025-08-09T05:03:18.745Z
Learning: After migrating to Zod v4 in the OpenFrontIO project, the standard import statement `import { z } from "zod"` should be used across the entire codebase, not `import { z } from "zod/v4"`.

Applied to files:

  • src/server/Archive.ts
📚 Learning: 2025-10-21T20:06:04.823Z
Learnt from: Saphereye
Repo: openfrontio/OpenFrontIO PR: 2233
File: src/client/HostLobbyModal.ts:891-891
Timestamp: 2025-10-21T20:06:04.823Z
Learning: For the HumansVsNations game mode in `src/client/HostLobbyModal.ts` and related files, the implementation strategy is to generate all nations and adjust their strength for balancing, rather than limiting lobby size based on the number of available nations on the map.

Applied to files:

  • tests/core/pathfinding/_fixtures.ts
  • src/client/HostLobbyModal.ts
📚 Learning: 2025-10-08T17:14:49.369Z
Learnt from: Foorack
Repo: openfrontio/OpenFrontIO PR: 2141
File: src/client/ClientGameRunner.ts:228-234
Timestamp: 2025-10-08T17:14:49.369Z
Learning: In `ClientGameRunner.ts`, the `myPlayer` field is always set when `shouldPreventWindowClose()` is called, so the null check in that method is sufficient without needing to fetch it again from `gameView.playerByClientID()`.

Applied to files:

  • src/client/ClientGameRunner.ts
📚 Learning: 2026-01-02T18:11:06.832Z
Learnt from: ryanbarlow97
Repo: openfrontio/OpenFrontIO PR: 2740
File: src/client/HostLobbyModal.ts:821-821
Timestamp: 2026-01-02T18:11:06.832Z
Learning: In src/client/HostLobbyModal.ts, the ?s=xxxxx URL suffix used in lobby URLs is purely for cache-busting platform previews (e.g., Discord, WhatsApp, x.com) and is not used by the join logic. The join flow ignores the suffix value, so regenerating it via updateUrlWithSuffix() on configuration changes will not break existing shared URLs; it only prompts platforms to refresh preview metadata. Treat the suffix as non-functional for join behavior and ensure any related changes preserve that invariant.

Applied to files:

  • src/client/HostLobbyModal.ts
📚 Learning: 2025-11-12T23:11:34.445Z
Learnt from: MaxHT0x
Repo: openfrontio/OpenFrontIO PR: 2262
File: src/core/configuration/DefaultConfig.ts:806-832
Timestamp: 2025-11-12T23:11:34.445Z
Learning: In src/core/configuration/DefaultConfig.ts, JSDoc documentation for configuration methods should not be added inline, as it was requested that documentation be placed elsewhere in the codebase.

Applied to files:

  • src/core/configuration/ConfigLoader.ts
  • src/core/configuration/DefaultConfig.ts
📚 Learning: 2026-01-08T13:52:00.939Z
Learnt from: deshack
Repo: openfrontio/OpenFrontIO PR: 2777
File: src/client/Main.ts:374-378
Timestamp: 2026-01-08T13:52:00.939Z
Learning: In src/client/Main.ts, ensure the browser back/forward navigation ordering is correctly handled: when popstate fires before hashchange during a navigation rollback (e.g., user cancels leaving an active game), use a preventHashUpdate-like flag to suppress the hashchange listener (onHashUpdate) from running in that rollback scenario. This avoids applying an unintended hash update after a rollback and keeps the UI/game state consistent. Document the flag’s usage and the exact conditions under which it is set/reset to prevent subtle regressions.

Applied to files:

  • src/client/Main.ts
🧬 Code graph analysis (17)
src/server/Logger.ts (1)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
src/client/Matchmaking.ts (1)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
src/server/Archive.ts (3)
src/core/game/GameImpl.ts (1)
  • config (357-359)
src/core/game/GameView.ts (1)
  • config (783-785)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
src/server/WorkerMetrics.ts (1)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
src/server/Server.ts (1)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
src/core/GameRunner.ts (4)
src/core/Schemas.ts (1)
  • ClientID (26-26)
src/core/game/GameMapLoader.ts (1)
  • GameMapLoader (4-6)
src/core/game/GameUpdates.ts (2)
  • GameUpdateViewData (17-23)
  • ErrorUpdate (25-28)
src/core/configuration/ConfigLoader.ts (1)
  • getConfig (10-26)
src/server/Worker.ts (3)
src/core/game/GameImpl.ts (1)
  • config (357-359)
src/core/game/GameView.ts (1)
  • config (783-785)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
src/server/MapPlaylist.ts (3)
src/core/game/GameImpl.ts (1)
  • config (357-359)
src/core/game/GameView.ts (1)
  • config (783-785)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
src/core/worker/WorkerClient.ts (2)
src/core/configuration/DefaultConfig.ts (1)
  • env (253-255)
src/core/configuration/DevConfig.ts (1)
  • env (23-25)
src/server/OtelResource.ts (1)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
vite.config.ts (2)
src/core/configuration/DefaultConfig.ts (1)
  • env (253-255)
src/core/configuration/DevConfig.ts (1)
  • env (23-25)
src/server/Master.ts (1)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
src/client/ClientGameRunner.ts (2)
src/core/configuration/ConfigLoader.ts (1)
  • getConfig (10-26)
src/core/configuration/Env.ts (1)
  • Env (58-133)
src/client/HostLobbyModal.ts (1)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
src/core/configuration/ConfigLoader.ts (4)
src/core/configuration/DefaultConfig.ts (4)
  • gameConfig (278-280)
  • userSettings (282-287)
  • isReplay (261-263)
  • DefaultConfig (243-1000)
src/core/configuration/DevConfig.ts (1)
  • DevConfig (50-59)
src/core/configuration/Env.ts (1)
  • Env (58-133)
src/core/configuration/PreprodConfig.ts (1)
  • preprodConfig (4-26)
src/core/configuration/DevConfig.ts (1)
src/core/configuration/DefaultConfig.ts (1)
  • isReplay (261-263)
src/client/Main.ts (1)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Deploy to openfront.dev
🔇 Additional comments (33)
package.json (1)

8-11: LGTM!

The environment value capitalization (Dev) now matches the GameEnv enum naming convention. All dev-related scripts are consistently updated.

src/core/configuration/Config.ts (2)

23-27: Simple typed enum for environment - clean approach.

Using a typed enum (GameEnv) with explicit values instead of string literals makes the environment handling type-safe and self-documenting.


70-71: LGTM!

Adding env(): GameEnv to the Config interface provides a clean way to access the runtime environment from game logic.

src/core/worker/WorkerMessages.ts (2)

1-1: LGTM!

Clean import of GameEnv type for use in the message interface.


40-45: Clean extension of InitMessage.

Adding env: GameEnv to the InitMessage interface allows the worker to receive environment context during initialization. Using the typed enum keeps the message contract type-safe.

index.html (1)

59-62: Config injection is safe: all values are server-controlled and JSON-stringified.

The serverConfig object contains only environment variables (GAME_ENV, NUM_WORKERS, GIT_COMMIT) set at build time, never user input. Using JSON.stringify() properly escapes dangerous characters, and the unescaped <%- syntax is correct for JSON output—escaping would break the JSON structure. No XSS risk.

src/core/configuration/DefaultConfig.ts (1)

246-255: Good simplification using typed value instead of complex object.

The change from a ServerConfig parameter to a GameEnv parameter makes the dependency simpler and cleaner. Using a typed enum value instead of a full configuration object is a better composition pattern. All call sites have been updated correctly to pass the GameEnv value.

.vscode/launch.json (1)

25-25: LGTM!

The environment value now matches the GameEnv.Dev enum key used in the new configuration system. This alignment is required for the ConfigLoader switch statement to work correctly.

src/client/graphics/layers/MultiTabModal.ts (1)

34-34: LGTM!

The environment check now uses the direct env() accessor on the game config, consistent with the unified configuration API. The GameEnv.Dev comparison is correct.

src/server/WorkerMetrics.ts (1)

7-7: LGTM!

Import and usage updated to the consolidated getServerConfig() API. The function behavior remains the same—it reads from Env.GAME_ENV and returns the appropriate ServerConfig.

Also applies to: 15-15

src/client/ClientGameRunner.ts (3)

1-1: LGTM!

The Env import provides access to GAME_ENV, which reads from window.SERVER_CONFIG (injected at build time) in the browser context. This is the correct pattern for the new configuration flow.


186-191: LGTM!

The getConfig call now passes Env.GAME_ENV as the second argument, matching the updated signature getConfig(gameConfig, gameEnv, userSettings, isReplay).


203-207: LGTM!

The WorkerClient constructor receives Env.GAME_ENV to propagate the environment to the worker, consistent with the updated worker initialization pattern.

src/server/Worker.ts (1)

10-10: LGTM!

The import rename from getServerConfigFromServer to getServerConfig is consistent with the broader refactor. The server-side config initialization at module level is appropriate here since workers run as separate processes.

Also applies to: 33-33

src/core/worker/WorkerClient.ts (2)

72-78: LGTM!

The env property is correctly included in the init message, matching the InitMessage type defined in WorkerMessages.ts.


23-27: LGTM! Constructor updated to accept GameEnv parameter.

The new env parameter makes environment passing explicit, avoiding runtime config lookups. The single call site in ClientGameRunner.ts (line 203) correctly passes the env parameter. Change is complete and consistent.

tests/core/pathfinding/_fixtures.ts (1)

149-154: LGTM!

The TestConfig constructor now receives GameEnv.Dev directly instead of a TestServerConfig instance. This simplifies test setup by removing the server config dependency.

src/server/MapPlaylist.ts (1)

1-1: LGTM!

The import and usage update from getServerConfigFromServer to getServerConfig is consistent with the broader refactor pattern seen across the codebase.

Also applies to: 20-20

src/server/Master.ts (1)

9-9: LGTM!

The import and call site updates align with the new getServerConfig() API. Calling it at module load time on the server is fine since Env.GAME_ENV is available at startup.

Also applies to: 15-15

src/core/GameRunner.ts (1)

36-43: LGTM!

The new env: GameEnv parameter is correctly passed to getConfig(). The signature now explicitly requires the environment, making configuration resolution clear and testable.

src/server/Archive.ts (1)

2-2: LGTM!

Import and usage updates are consistent with the new config API pattern used across the server codebase.

Also applies to: 13-13

src/server/OtelResource.ts (1)

6-8: LGTM!

Import and call site updates follow the same pattern as other server modules.

src/core/worker/Worker.worker.ts (1)

44-56: Code change is correct. The message.env parameter matches the updated createGameRunner signature and InitMessage type includes the env: GameEnv property as expected.

tests/util/Setup.ts (1)

3-3: LGTM!

Clean switch from ServerConfig to GameEnv.Dev. The test setup now aligns with the new environment-based configuration pattern.

Also applies to: 73-78

src/server/Logger.ts (1)

10-14: LGTM!

Import and call site updated to use the renamed getServerConfig() API. Server-side module initialization is fine here.

src/core/configuration/DevConfig.ts (1)

50-58: LGTM!

Good refactoring to accept GameEnv instead of ServerConfig. Using a typed enum keeps the constructor simple and avoids passing object dependencies when a plain value suffices.

src/client/HostLobbyModal.ts (2)

1159-1159: Same concern: verify if await is necessary for getServerConfig().

Per the earlier comment on Matchmaking.ts, if getServerConfig() is synchronous, these await calls are unnecessary. The verification script in the other file will cover this.

Also applies to: 1181-1181, 1234-1234


1248-1252: Nice improvement to error handling.

Good addition of response.ok check with error text logging before throwing. This makes debugging failed lobby creation much easier.

src/client/Matchmaking.ts (1)

146-146: Remove unnecessary await from synchronous getServerConfig() calls.

getServerConfig() returns ServerConfig synchronously, not a Promise. Remove await at lines 146 and 225.

Also applies to: 225-225

⛔ Skipped due to learnings
Learnt from: Foorack
Repo: openfrontio/OpenFrontIO PR: 2141
File: src/client/ClientGameRunner.ts:228-234
Timestamp: 2025-10-08T17:14:49.369Z
Learning: In `ClientGameRunner.ts`, the `myPlayer` field is always set when `shouldPreventWindowClose()` is called, so the null check in that method is sufficient without needing to fetch it again from `gameView.playerByClientID()`.
src/client/JoinPrivateLobbyModal.ts (1)

572-575: Good simplification using Env.GIT_COMMIT.

Replacing the fetch-based git commit retrieval with Env.GIT_COMMIT is cleaner. The version check logic is preserved correctly.

src/core/configuration/Env.ts (1)

9-13: Good use of a simple interface for config vars.

Clean, typed interface. This is the right approach over class hierarchies.

tests/pathfinding/utils.ts (1)

251-271: Test config updated correctly for new constructor signature.

The change to pass GameEnv.Dev as the second argument aligns with the refactored TestConfig and DefaultConfig constructors shown in the relevant code snippets.

src/core/configuration/ConfigLoader.ts (1)

28-43: getServerConfig() is now synchronous - good simplification.

The function reads Env.GAME_ENV directly and returns the appropriate config. This is cleaner than the previous async approach. The switch cases cover all GameEnv values correctly.

Comment on lines +105 to 128
get GIT_COMMIT(): string | undefined {
try {
if (typeof window !== "undefined" && window.SERVER_CONFIG) {
return window.SERVER_CONFIG.gitCommit;
}
} catch {
// Ignore errors accessing window
}
return getEnv("GIT_COMMIT");
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n "Env\.GIT_COMMIT" --type=ts -C2

Repository: openfrontio/OpenFrontIO

Length of output: 682


🏁 Script executed:

# Also check the exact usage at JoinPrivateLobbyModal.ts:572
fd "JoinPrivateLobbyModal.ts" --type=f -x cat -n {} | sed -n '565,580p'

Repository: openfrontio/OpenFrontIO

Length of output: 728


🏁 Script executed:

# Check the Env.ts file to see the exact return type and implementation
fd "Env.ts" --type=f -path "*/core/configuration/*" -x cat -n {}

Repository: openfrontio/OpenFrontIO

Length of output: 235


🏁 Script executed:

cat -n src/core/configuration/Env.ts | head -120

Repository: openfrontio/OpenFrontIO

Length of output: 3955


🏁 Script executed:

# Search for all GIT_COMMIT usages without limiting to ts type
rg -n "GIT_COMMIT" -C2

Repository: openfrontio/OpenFrontIO

Length of output: 2824


🏁 Script executed:

# Check if there are other problematic usages of GIT_COMMIT without null handling
rg -n "myGitCommit|Env\.GIT_COMMIT" --type-list all -B3 -A3

Repository: openfrontio/OpenFrontIO

Length of output: 456


🏁 Script executed:

# Check all usages of myGitCommit and Env.GIT_COMMIT
rg -n "myGitCommit|Env\.GIT_COMMIT" -B3 -A3

Repository: openfrontio/OpenFrontIO

Length of output: 1273


Handle undefined git commit in version comparison

Env.GIT_COMMIT returns string | undefined. In JoinPrivateLobbyModal.ts at line 575, the comparison myGitCommit !== "DEV" will evaluate to true when myGitCommit is undefined, causing the code to enter the version mismatch check even when the git commit is missing. This can trigger false version mismatch errors.

Use a fallback value instead, similar to DefaultConfig.ts line 153 which uses Env.GIT_COMMIT ?? "", or add explicit undefined handling:

const myGitCommit = Env.GIT_COMMIT ?? "DEV";

This ensures undefined cases are treated as development builds and won't cause unexpected version errors.

🤖 Prompt for AI Agents
In @src/core/configuration/Env.ts around lines 105 - 114, Env.GIT_COMMIT can be
undefined, so in JoinPrivateLobbyModal where myGitCommit is compared to "DEV"
you must normalize the value before comparing (e.g., set myGitCommit =
Env.GIT_COMMIT ?? "DEV" or otherwise handle undefined explicitly) to avoid
treating undefined as a non-DEV commit; mirror the approach used in
DefaultConfig (Env.GIT_COMMIT ?? "") and update the comparison logic that
currently checks myGitCommit !== "DEV" to use the normalized value.

Copy link
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: 4

🤖 Fix all issues with AI agents
In @src/core/configuration/ConfigLoader.ts:
- Around line 23-25: The default case in the switch that throws an error uses
the global Env.GAME_ENV instead of the switch parameter gameEnv; update the
thrown Error in ConfigLoader (the switch handling gameEnv) to reference the
function parameter (gameEnv) so the message accurately reports the unsupported
value (e.g., throw Error(`unsupported server configuration: ${gameEnv}`)).

In @src/core/configuration/Env.ts:
- Around line 121-132: The NUM_WORKERS getter currently returns a string by
calling String(window.SERVER_CONFIG.numWorkers) but ServerConfigVars.numWorkers
is typed as number, causing a type mismatch; update the getter to return a
number (or number | undefined) by removing the String() conversion and
coercing/parsing as needed (e.g., return window.SERVER_CONFIG.numWorkers
directly when present and otherwise parse or convert the value from
getEnv("NUM_WORKERS") to a number), ensuring the getter's TypeScript signature
matches the chosen return type; adjust any callers or the getter's declared type
accordingly so NUM_WORKERS and ServerConfigVars.numWorkers are consistent.
- Line 64: The enum lookup GameEnv[window.SERVER_CONFIG.gameEnv as keyof typeof
GameEnv] is unsafe and can return undefined; update the getter(s) to validate
the raw string from window.SERVER_CONFIG.gameEnv (and the similar lookups at the
other two sites) before casting—check whether the string is a valid key/value of
GameEnv (e.g., via Object.keys/values or a membership check), and if invalid
throw a clear error including the invalid value; ensure the function(s) that use
GameEnv (the lines referencing GameEnv[...] at line 64 and the similar lookups
at lines 78 and 84) return a valid GameEnv or raise an error instead of
returning undefined.
- Around line 59-85: The GAME_ENV getter on Env currently logs every access
which floods logs; change it to compute the environment once and cache the
result in a private static field (e.g. add a private static _cachedGameEnv:
GameEnv | null) and have GAME_ENV return that cached value after the first
resolution; remove or replace the console.log calls inside GAME_ENV and ensure
the final fallback still uses getEnv("GAME_ENV") and assigns the resolved enum
value to _cachedGameEnv before returning.
🧹 Nitpick comments (2)
vite.config.ts (1)

59-63: Consider adding a fallback for parseInt to avoid NaN.

If env.NUM_WORKERS is undefined or not a valid number in production, parseInt will return NaN. This could cause unexpected behavior downstream.

🔧 Suggested fix with fallback
            serverConfig: JSON.stringify({
              gameEnv: isProduction ? env.GAME_ENV : "Dev",
-             numWorkers: isProduction ? parseInt(env.NUM_WORKERS, 10) : 2,
+             numWorkers: isProduction ? (parseInt(env.NUM_WORKERS, 10) || 1) : 2,
              gitCommit: isProduction ? env.GIT_COMMIT : "DEV",
            }),
src/core/configuration/ConfigLoader.ts (1)

31-39: Inconsistent instantiation: Dev creates new instance, others use singletons.

preprodConfig and prodConfig are reused singleton objects, but DevServerConfig is instantiated fresh on every call. The parent DefaultServerConfig caches the publicKey after fetching JWKS. Creating new instances could cause repeated JWKS fetches.

Consider making DevServerConfig a singleton like the others for consistency.

Proposed fix
+const devServerConfig = new DevServerConfig();
+
 export function getServerConfig(): ServerConfig {
   const gameEnv = Env.GAME_ENV;
   switch (gameEnv) {
     case GameEnv.Dev:
       console.log("using dev server config");
-      return new DevServerConfig();
+      return devServerConfig;
     case GameEnv.Preprod:
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c4a6aea and 37c5410.

📒 Files selected for processing (32)
  • .vscode/launch.json
  • .vscode/settings.json
  • index.html
  • nginx.conf
  • package.json
  • src/client/ClientGameRunner.ts
  • src/client/HostLobbyModal.ts
  • src/client/JoinPrivateLobbyModal.ts
  • src/client/Main.ts
  • src/client/Matchmaking.ts
  • src/client/graphics/layers/MultiTabModal.ts
  • src/core/GameRunner.ts
  • src/core/configuration/Config.ts
  • src/core/configuration/ConfigLoader.ts
  • src/core/configuration/DefaultConfig.ts
  • src/core/configuration/DevConfig.ts
  • src/core/configuration/Env.ts
  • src/core/worker/Worker.worker.ts
  • src/core/worker/WorkerClient.ts
  • src/core/worker/WorkerMessages.ts
  • src/server/Archive.ts
  • src/server/Logger.ts
  • src/server/MapPlaylist.ts
  • src/server/Master.ts
  • src/server/OtelResource.ts
  • src/server/Server.ts
  • src/server/Worker.ts
  • src/server/WorkerMetrics.ts
  • tests/core/pathfinding/_fixtures.ts
  • tests/pathfinding/utils.ts
  • tests/util/Setup.ts
  • vite.config.ts
💤 Files with no reviewable changes (1)
  • nginx.conf
✅ Files skipped from review due to trivial changes (1)
  • .vscode/settings.json
🚧 Files skipped from review as they are similar to previous changes (11)
  • src/server/Server.ts
  • src/core/worker/WorkerClient.ts
  • src/core/GameRunner.ts
  • src/client/Matchmaking.ts
  • tests/core/pathfinding/_fixtures.ts
  • index.html
  • src/client/Main.ts
  • .vscode/launch.json
  • src/server/MapPlaylist.ts
  • tests/util/Setup.ts
  • tests/pathfinding/utils.ts
🧰 Additional context used
🧠 Learnings (12)
📚 Learning: 2026-01-12T21:37:01.156Z
Learnt from: FloPinguin
Repo: openfrontio/OpenFrontIO PR: 2874
File: src/server/MapLandTiles.ts:7-11
Timestamp: 2026-01-12T21:37:01.156Z
Learning: In this repository's OpenFrontIO deployment, inter-service HTTP calls to the master should target http://localhost:3000 (master at port 3000) as the canonical address. Apply this as the standard for all server-side TypeScript code that communicates with the master. Avoid hardcoding non-master URLs; centralize the master address (e.g., via config or env) when possible, and ensure internal service communication uses localhost:3000 in this architecture.

Applied to files:

  • src/server/Logger.ts
  • src/server/Master.ts
  • src/server/Worker.ts
  • src/core/worker/WorkerMessages.ts
  • src/server/Archive.ts
  • src/server/WorkerMetrics.ts
  • vite.config.ts
  • src/server/OtelResource.ts
  • src/core/configuration/Config.ts
  • src/core/configuration/DefaultConfig.ts
  • src/client/HostLobbyModal.ts
  • src/core/configuration/DevConfig.ts
  • src/core/configuration/ConfigLoader.ts
  • src/client/JoinPrivateLobbyModal.ts
  • src/client/graphics/layers/MultiTabModal.ts
  • src/client/ClientGameRunner.ts
  • src/core/configuration/Env.ts
  • src/core/worker/Worker.worker.ts
📚 Learning: 2025-08-09T05:03:18.745Z
Learnt from: scottanderson
Repo: openfrontio/OpenFrontIO PR: 1761
File: src/core/ApiSchemas.ts:2-2
Timestamp: 2025-08-09T05:03:18.745Z
Learning: After migrating to Zod v4 in the OpenFrontIO project, the standard import statement `import { z } from "zod"` should be used across the entire codebase, not `import { z } from "zod/v4"`.

Applied to files:

  • src/server/Archive.ts
📚 Learning: 2025-10-20T20:15:28.858Z
Learnt from: sambokai
Repo: openfrontio/OpenFrontIO PR: 2225
File: src/core/execution/FakeHumanExecution.ts:51-51
Timestamp: 2025-10-20T20:15:28.858Z
Learning: In src/core/execution/FakeHumanExecution.ts, game balance constants like MIRV_COOLDOWN_TICKS, MIRV_HESITATION_ODDS, VICTORY_DENIAL_TEAM_THRESHOLD, VICTORY_DENIAL_INDIVIDUAL_THRESHOLD, and STEAMROLL_CITY_GAP_MULTIPLIER are experimental tuning parameters subject to frequent change during balance testing. Do not flag changes to these values as issues or compare them against previous values.

Applied to files:

  • vite.config.ts
📚 Learning: 2025-11-12T23:11:34.445Z
Learnt from: MaxHT0x
Repo: openfrontio/OpenFrontIO PR: 2262
File: src/core/configuration/DefaultConfig.ts:806-832
Timestamp: 2025-11-12T23:11:34.445Z
Learning: In src/core/configuration/DefaultConfig.ts, JSDoc documentation for configuration methods should not be added inline, as it was requested that documentation be placed elsewhere in the codebase.

Applied to files:

  • src/core/configuration/DefaultConfig.ts
  • src/core/configuration/DevConfig.ts
  • src/core/configuration/ConfigLoader.ts
📚 Learning: 2025-06-09T02:20:43.637Z
Learnt from: VariableVince
Repo: openfrontio/OpenFrontIO PR: 1110
File: src/client/Main.ts:293-295
Timestamp: 2025-06-09T02:20:43.637Z
Learning: In src/client/Main.ts, during game start in the handleJoinLobby callback, UI elements are hidden using direct DOM manipulation with classList.add("hidden") for consistency. This includes modals, buttons, and error divs. The codebase follows this pattern rather than using component APIs for hiding elements during game transitions.

Applied to files:

  • src/client/HostLobbyModal.ts
  • src/client/JoinPrivateLobbyModal.ts
📚 Learning: 2026-01-02T18:11:06.832Z
Learnt from: ryanbarlow97
Repo: openfrontio/OpenFrontIO PR: 2740
File: src/client/HostLobbyModal.ts:821-821
Timestamp: 2026-01-02T18:11:06.832Z
Learning: In src/client/HostLobbyModal.ts, the ?s=xxxxx URL suffix used in lobby URLs is purely for cache-busting platform previews (e.g., Discord, WhatsApp, x.com) and is not used by the join logic. The join flow ignores the suffix value, so regenerating it via updateUrlWithSuffix() on configuration changes will not break existing shared URLs; it only prompts platforms to refresh preview metadata. Treat the suffix as non-functional for join behavior and ensure any related changes preserve that invariant.

Applied to files:

  • src/client/HostLobbyModal.ts
📚 Learning: 2025-10-21T20:06:04.823Z
Learnt from: Saphereye
Repo: openfrontio/OpenFrontIO PR: 2233
File: src/client/HostLobbyModal.ts:891-891
Timestamp: 2025-10-21T20:06:04.823Z
Learning: For the HumansVsNations game mode in `src/client/HostLobbyModal.ts` and related files, the implementation strategy is to generate all nations and adjust their strength for balancing, rather than limiting lobby size based on the number of available nations on the map.

Applied to files:

  • src/client/HostLobbyModal.ts
📚 Learning: 2025-12-26T22:21:21.904Z
Learnt from: FloPinguin
Repo: openfrontio/OpenFrontIO PR: 2689
File: src/client/PublicLobby.ts:245-245
Timestamp: 2025-12-26T22:21:21.904Z
Learning: In public lobbies with HumansVsNations mode in src/client/PublicLobby.ts, maxPlayers represents only human player slots (already halved in DefaultConfig.ts). The nation NPCs are added automatically server-side and don't count toward maxPlayers. Therefore, getTeamSize correctly returns maxPlayers directly for HumansVsNations to display the proper team size (e.g., maxPlayers=5 yields "5 Humans vs 5 Nations").

Applied to files:

  • src/client/HostLobbyModal.ts
  • src/client/JoinPrivateLobbyModal.ts
📚 Learning: 2025-08-12T00:31:50.144Z
Learnt from: scottanderson
Repo: openfrontio/OpenFrontIO PR: 1752
File: src/core/game/Game.ts:750-752
Timestamp: 2025-08-12T00:31:50.144Z
Learning: In the OpenFrontIO codebase, changes to the PlayerInteraction interface (like adding canDonateGold and canDonateTroops flags) do not require corresponding updates to src/core/Schemas.ts or server serialization code.

Applied to files:

  • src/client/HostLobbyModal.ts
  • src/core/configuration/DevConfig.ts
📚 Learning: 2026-01-02T18:11:15.132Z
Learnt from: ryanbarlow97
Repo: openfrontio/OpenFrontIO PR: 2740
File: src/client/HostLobbyModal.ts:821-821
Timestamp: 2026-01-02T18:11:15.132Z
Learning: In src/client/HostLobbyModal.ts, the `?s=xxxxx` URL suffix in lobby URLs is purely for cache-busting embed previews on platforms like Discord, WhatsApp, and x.com. The suffix value is ignored by the join logic (any value works), so regenerating it on config changes via `updateUrlWithSuffix()` doesn't break existing shared URLs - it only forces platforms to re-fetch updated preview metadata.

Applied to files:

  • src/client/JoinPrivateLobbyModal.ts
📚 Learning: 2025-10-08T17:14:49.369Z
Learnt from: Foorack
Repo: openfrontio/OpenFrontIO PR: 2141
File: src/client/ClientGameRunner.ts:228-234
Timestamp: 2025-10-08T17:14:49.369Z
Learning: In `ClientGameRunner.ts`, the `myPlayer` field is always set when `shouldPreventWindowClose()` is called, so the null check in that method is sufficient without needing to fetch it again from `gameView.playerByClientID()`.

Applied to files:

  • src/client/ClientGameRunner.ts
📚 Learning: 2025-06-16T03:03:59.778Z
Learnt from: VariableVince
Repo: openfrontio/OpenFrontIO PR: 1192
File: src/client/graphics/layers/RadialMenuElements.ts:312-314
Timestamp: 2025-06-16T03:03:59.778Z
Learning: In RadialMenuElements.ts, when fixing undefined params errors in subMenu functions, use explicit checks like `if (params === undefined || params.selected === null)` rather than optional chaining, as it makes the intent clearer and matches the specific error scenarios where params can be undefined during spawn phase operations.

Applied to files:

  • src/core/configuration/Env.ts
🧬 Code graph analysis (11)
src/server/Logger.ts (3)
src/core/game/GameImpl.ts (1)
  • config (357-359)
src/core/game/GameView.ts (1)
  • config (783-785)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
src/server/Master.ts (1)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
src/server/Worker.ts (3)
src/core/game/GameImpl.ts (1)
  • config (357-359)
src/core/game/GameView.ts (1)
  • config (783-785)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
src/server/Archive.ts (3)
src/core/game/GameImpl.ts (1)
  • config (357-359)
src/core/game/GameView.ts (1)
  • config (783-785)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
src/server/WorkerMetrics.ts (1)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
vite.config.ts (2)
src/core/configuration/DefaultConfig.ts (1)
  • env (253-255)
src/core/configuration/DevConfig.ts (1)
  • env (23-25)
src/server/OtelResource.ts (1)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
src/client/HostLobbyModal.ts (1)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
src/core/configuration/ConfigLoader.ts (5)
src/core/configuration/DefaultConfig.ts (4)
  • gameConfig (278-280)
  • userSettings (282-287)
  • isReplay (261-263)
  • DefaultConfig (243-1000)
src/core/configuration/Config.ts (2)
  • Config (70-181)
  • ServerConfig (29-63)
src/core/configuration/DevConfig.ts (2)
  • DevConfig (50-59)
  • DevServerConfig (6-48)
src/core/configuration/Env.ts (1)
  • Env (58-133)
src/core/configuration/PreprodConfig.ts (1)
  • preprodConfig (4-26)
src/client/JoinPrivateLobbyModal.ts (2)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
src/core/configuration/Env.ts (1)
  • Env (58-133)
src/client/ClientGameRunner.ts (4)
src/core/game/GameImpl.ts (1)
  • config (357-359)
src/core/game/GameView.ts (1)
  • config (783-785)
src/core/configuration/ConfigLoader.ts (1)
  • getConfig (10-26)
src/core/configuration/Env.ts (1)
  • Env (58-133)
🔇 Additional comments (27)
package.json (1)

8-11: LGTM!

The GAME_ENV=Dev change aligns with the GameEnv.Dev enum value, keeping naming consistent across the codebase.

src/core/configuration/Config.ts (2)

23-27: LGTM! Clean enum definition.

Simple typed union via enum is a good pattern here.


70-71: All Config implementations properly updated with the env() method.

Verification confirms:

  • DefaultConfig (implements Config) includes env(): GameEnv
  • DefaultServerConfig (abstract, implements ServerConfig) defines abstract env(): GameEnv
  • All concrete subclasses (DevConfig, ProdConfig, PreprodConfig) implement env()
  • TestServerConfig (test implementation) includes env()

The breaking change is complete across the codebase. No implementations are missing the method.

src/core/worker/WorkerMessages.ts (2)

1-1: LGTM!

Import is correctly scoped to just the GameEnv enum.


40-45: LGTM! Clean interface extension.

Adding env: GameEnv to InitMessage properly threads the environment through worker initialization.

src/client/graphics/layers/MultiTabModal.ts (1)

30-37: LGTM! Cleaner access pattern.

The change from config().serverConfig().env() to config().env() simplifies the call chain while preserving the same behavior.

src/server/WorkerMetrics.ts (1)

7-7: LGTM!

The import and usage are updated correctly. Good practice to retrieve config inside the function rather than at module level—this ensures Env.GAME_ENV is set when the function runs.

Also applies to: 15-15

src/server/Worker.ts (1)

10-10: LGTM!

The migration to getServerConfig() is correct. Module-level initialization works fine for server-side code since environment variables are set before the process starts.

Also applies to: 33-33

src/server/Archive.ts (1)

2-2: LGTM!

Clean migration to the new getServerConfig() API. Consistent with the pattern used in other server modules.

Also applies to: 13-13

src/server/OtelResource.ts (1)

6-6: LGTM!

Straightforward API migration. The module-level config pattern is consistent with other server files.

Also applies to: 8-8

src/server/Master.ts (1)

9-9: LGTM!

The API migration is correct. The removal of the /api/env endpoint (as noted in the summary) aligns with the new approach of injecting server config via window.SERVER_CONFIG on the client side.

Also applies to: 15-15

src/client/ClientGameRunner.ts (3)

1-1: LGTM!

Clean import of Env for accessing the game environment. This aligns well with the refactor to use a single source of truth for configuration.


186-191: LGTM!

Passing Env.GAME_ENV as the second parameter to getConfig is consistent with the updated API. The environment is now sourced from window.SERVER_CONFIG (injected at build time) or falls back appropriately.


203-207: LGTM!

The WorkerClient now receives Env.GAME_ENV as the second parameter, threading the environment through to the worker initialization. This keeps the configuration flow consistent.

src/core/worker/Worker.worker.ts (1)

44-50: LGTM!

The worker now receives env from the init message and passes it to createGameRunner. This keeps the environment configuration consistent between main thread and worker.

src/server/Logger.ts (1)

10-14: LGTM!

The rename from getServerConfigFromServer to getServerConfig simplifies the API surface. On the server side, Env.GAME_ENV will read from environment variables since window is not available.

src/client/JoinPrivateLobbyModal.ts (5)

3-3: LGTM!

Clean import of Env for accessing configuration values like GIT_COMMIT.


12-12: LGTM!

The rename from getServerConfigFromClient to getServerConfig consolidates the API. On the client side, Env.GAME_ENV reads from window.SERVER_CONFIG which is injected at build time.


515-516: LGTM!

Using getServerConfig() here is consistent with the refactored API.


549-582: Nice simplification!

Replacing the parallel git commit fetch with Env.GIT_COMMIT removes async complexity. The git commit is now synchronously available from window.SERVER_CONFIG.gitCommit, which is injected at build time.


601-603: LGTM!

Consistent use of getServerConfig() for the worker path lookup.

src/client/HostLobbyModal.ts (4)

4-4: LGTM!

The rename from getServerConfigFromClient to getServerConfig keeps the API surface clean and consistent.


1159-1161: LGTM!

Consistent use of getServerConfig() for the worker path in game start.


1181-1182: LGTM!

Consistent use of getServerConfig() for the worker path in player polling.


1233-1258: Good improvement on error handling!

The createLobby function now checks response.ok and logs the server error text before throwing. This makes debugging failed lobby creation much easier.

src/core/configuration/DefaultConfig.ts (1)

246-256: Clean simplification - LGTM!

Replacing the full ServerConfig dependency with just the GameEnv enum is a solid refactor. The class now holds only what it needs. Good use of composition.

src/core/configuration/DevConfig.ts (1)

50-58: LGTM!

Clean pass-through of gameEnv to the parent class. This aligns well with the DefaultConfig constructor changes.

try {
if (typeof window !== "undefined" && window.SERVER_CONFIG) {
console.log("using server config from window");
return GameEnv[window.SERVER_CONFIG.gameEnv as keyof typeof GameEnv];
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unsafe enum lookup may return undefined.

If window.SERVER_CONFIG.gameEnv contains an unexpected string (typo, misconfiguration), this lookup returns undefined while the return type says GameEnv. This could cause hard-to-debug issues downstream.

Proposed fix: validate and throw on invalid value
-        return GameEnv[window.SERVER_CONFIG.gameEnv as keyof typeof GameEnv];
+        const env = GameEnv[window.SERVER_CONFIG.gameEnv as keyof typeof GameEnv];
+        if (env === undefined) {
+          throw new Error(`Invalid GAME_ENV value: ${window.SERVER_CONFIG.gameEnv}`);
+        }
+        return env;

Apply similar validation at lines 78 and 84.

📝 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
return GameEnv[window.SERVER_CONFIG.gameEnv as keyof typeof GameEnv];
const env = GameEnv[window.SERVER_CONFIG.gameEnv as keyof typeof GameEnv];
if (env === undefined) {
throw new Error(`Invalid GAME_ENV value: ${window.SERVER_CONFIG.gameEnv}`);
}
return env;
🤖 Prompt for AI Agents
In @src/core/configuration/Env.ts at line 64, The enum lookup
GameEnv[window.SERVER_CONFIG.gameEnv as keyof typeof GameEnv] is unsafe and can
return undefined; update the getter(s) to validate the raw string from
window.SERVER_CONFIG.gameEnv (and the similar lookups at the other two sites)
before casting—check whether the string is a valid key/value of GameEnv (e.g.,
via Object.keys/values or a membership check), and if invalid throw a clear
error including the invalid value; ensure the function(s) that use GameEnv (the
lines referencing GameEnv[...] at line 64 and the similar lookups at lines 78
and 84) return a valid GameEnv or raise an error instead of returning undefined.

Comment on lines +121 to +146
get NUM_WORKERS() {
// Check window.SERVER_CONFIG first (injected at build time)
try {
if (typeof window !== "undefined" && window.SERVER_CONFIG) {
return String(window.SERVER_CONFIG.numWorkers);
}
} catch {
// Ignore errors accessing window
}

return getEnv("NUM_WORKERS");
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

NUM_WORKERS returns string but interface defines number.

ServerConfigVars.numWorkers is typed as number (line 11), but this getter converts it to String() at line 125. This inconsistency may confuse callers. Either:

  • Return number | undefined and skip the String() conversion, or
  • Document/type the getter return as string | undefined if string is intentional
🤖 Prompt for AI Agents
In @src/core/configuration/Env.ts around lines 121 - 132, The NUM_WORKERS getter
currently returns a string by calling String(window.SERVER_CONFIG.numWorkers)
but ServerConfigVars.numWorkers is typed as number, causing a type mismatch;
update the getter to return a number (or number | undefined) by removing the
String() conversion and coercing/parsing as needed (e.g., return
window.SERVER_CONFIG.numWorkers directly when present and otherwise parse or
convert the value from getEnv("NUM_WORKERS") to a number), ensuring the getter's
TypeScript signature matches the chosen return type; adjust any callers or the
getter's declared type accordingly so NUM_WORKERS and
ServerConfigVars.numWorkers are consistent.

Copy link
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: 4

🤖 Fix all issues with AI agents
In @.github/workflows/deploy.yml:
- Line 113: The ENV value in the workflow currently emits capitalized
'Prod'/'Staging', which will be rejected by the downstream scripts; change the
GitHub Actions expression that sets ENV so it outputs lowercase 'prod' and
'staging' instead of 'Prod'/'Staging' (i.e., update the ENV assignment
expression that uses inputs.target_domain to return 'prod' or 'staging'),
ensuring consistency with the strict lowercase checks in build-deploy.sh,
build.sh, and deploy.sh.

In @src/core/configuration/ConfigLoader.ts:
- Around line 23-25: The default case in the switch inside ConfigLoader.ts
throws an error referencing Env.GAME_ENV instead of the actual function
parameter; update the throw in that default branch to include the incoming
gameEnv variable (the parameter named gameEnv) so the error message reflects the
actual argument that triggered the unsupported configuration, e.g. replace
Env.GAME_ENV with gameEnv in the thrown Error in the default case of the switch.

In @src/core/configuration/Env.ts:
- Around line 59-85: The GAME_ENV getter can return undefined when mapping
strings to the GameEnv enum; validate each lookup (for
window.SERVER_CONFIG.gameEnv, import.meta.env.MODE, and getEnv("GAME_ENV")) by
checking that the mapped value is a valid GameEnv member before returning it,
and otherwise fall back to a safe default or throw a clear error; update the get
GAME_ENV implementation to compute the candidate key, verify via something like
Object.hasOwnProperty or a reverse-lookup on GameEnv, and only return
GameEnv[candidate] when valid, otherwise continue to the next source or return a
validated default.

In @vite.config.ts:
- Around line 59-63: The serverConfig construction sets numWorkers using
parseInt(env.NUM_WORKERS, 10) which yields NaN if NUM_WORKERS is missing in
production; update the numWorkers logic in the serverConfig block to use a safe
fallback (e.g., parseInt(env.NUM_WORKERS || "2", 10) or
parseInt(env.NUM_WORKERS, 10) with a Number.isNaN check) so production falls
back to 2 when env.NUM_WORKERS is undefined or invalid.
🧹 Nitpick comments (6)
src/core/configuration/PreprodConfig.ts (1)

4-7: Consider renaming file and class to match the new Staging enum value.

The file is named PreprodConfig.ts and the class is preprodConfig, but it now returns GameEnv.Staging. This mismatch might confuse future maintainers. Consider renaming to StagingConfig.ts and stagingConfig for clarity.

src/client/JoinPrivateLobbyModal.ts (1)

514-516: Repeated getServerConfig() calls may cause console noise.

Looking at getServerConfig() in ConfigLoader.ts (lines 27-42), it logs "using X server config" on every call. Since pollPlayers runs every second and also calls getServerConfig(), you will see this log message repeatedly in the console.

Consider caching the config at the class level or module level to avoid repeated logging.

🔧 Suggested fix: cache config at class level
 export class JoinPrivateLobbyModal extends BaseModal {
+  private serverConfig = getServerConfig();
   @query("#lobbyIdInput") private lobbyIdInput!: HTMLInputElement;

Then use this.serverConfig instead of calling getServerConfig() in checkActiveLobby and pollPlayers.

src/client/HostLobbyModal.ts (2)

1180-1182: Same console noise issue in pollPlayers.

This method runs every second (via interval at line 867), and getServerConfig() logs to console on each call. This will produce repeated log messages.

Consider caching the server config at class or module level.

🔧 Suggested fix: cache at class level
 export class HostLobbyModal extends BaseModal {
+  private serverConfig = getServerConfig();
   @state() private selectedMap: GameMapType = GameMapType.World;

Then use this.serverConfig in startGame, pollPlayers, and pass to createLobby.


1233-1234: Consider accepting config as parameter.

The createLobby function could receive the config as a parameter instead of calling getServerConfig() internally. This would make the function more testable and avoid the logging side effect.

🔧 Suggested change
-async function createLobby(creatorClientID: string): Promise<GameInfo> {
-  const config = getServerConfig();
+async function createLobby(creatorClientID: string, config: ServerConfig): Promise<GameInfo> {

Then call it with the cached config from the class.

src/core/configuration/Env.ts (2)

63-63: Remove or guard debug logging in frequently accessed getter.

The console.log statements on lines 63, 77, and 83 run every time Env.GAME_ENV is accessed. This creates log noise in production. Consider removing these or logging only once (e.g., cache the result after first access).

Also applies to: 77-77, 83-83


121-132: Consider returning number | undefined instead of string | undefined.

ServerConfigVars.numWorkers is typed as number (line 11), but NUM_WORKERS getter converts it to a string (line 125). Callers expecting a number will need to parse it back. Returning number | undefined would be more consistent with the interface definition and avoid extra parsing downstream.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 37c5410 and d56fc5b.

📒 Files selected for processing (34)
  • .github/workflows/deploy.yml
  • .vscode/launch.json
  • .vscode/settings.json
  • index.html
  • nginx.conf
  • package.json
  • src/client/ClientGameRunner.ts
  • src/client/HostLobbyModal.ts
  • src/client/JoinPrivateLobbyModal.ts
  • src/client/Main.ts
  • src/client/Matchmaking.ts
  • src/client/graphics/layers/MultiTabModal.ts
  • src/core/GameRunner.ts
  • src/core/configuration/Config.ts
  • src/core/configuration/ConfigLoader.ts
  • src/core/configuration/DefaultConfig.ts
  • src/core/configuration/DevConfig.ts
  • src/core/configuration/Env.ts
  • src/core/configuration/PreprodConfig.ts
  • src/core/worker/Worker.worker.ts
  • src/core/worker/WorkerClient.ts
  • src/core/worker/WorkerMessages.ts
  • src/server/Archive.ts
  • src/server/Logger.ts
  • src/server/MapPlaylist.ts
  • src/server/Master.ts
  • src/server/OtelResource.ts
  • src/server/Server.ts
  • src/server/Worker.ts
  • src/server/WorkerMetrics.ts
  • tests/core/pathfinding/_fixtures.ts
  • tests/pathfinding/utils.ts
  • tests/util/Setup.ts
  • vite.config.ts
💤 Files with no reviewable changes (1)
  • nginx.conf
✅ Files skipped from review due to trivial changes (1)
  • .vscode/settings.json
🚧 Files skipped from review as they are similar to previous changes (13)
  • src/client/Matchmaking.ts
  • src/server/MapPlaylist.ts
  • src/server/Logger.ts
  • src/server/Worker.ts
  • src/core/GameRunner.ts
  • src/core/worker/WorkerClient.ts
  • src/server/Master.ts
  • src/client/ClientGameRunner.ts
  • index.html
  • src/server/Server.ts
  • tests/core/pathfinding/_fixtures.ts
  • tests/util/Setup.ts
  • src/client/Main.ts
🧰 Additional context used
🧠 Learnings (13)
📓 Common learnings
Learnt from: scottanderson
Repo: openfrontio/OpenFrontIO PR: 1752
File: src/core/configuration/DefaultConfig.ts:326-328
Timestamp: 2025-08-12T00:30:20.374Z
Learning: In the OpenFrontIO codebase, game configurations are generated at runtime rather than loaded from saved files, so configuration properties will always be present and don't require fallback defaults for backward compatibility.
📚 Learning: 2026-01-12T21:37:01.156Z
Learnt from: FloPinguin
Repo: openfrontio/OpenFrontIO PR: 2874
File: src/server/MapLandTiles.ts:7-11
Timestamp: 2026-01-12T21:37:01.156Z
Learning: In this repository's OpenFrontIO deployment, inter-service HTTP calls to the master should target http://localhost:3000 (master at port 3000) as the canonical address. Apply this as the standard for all server-side TypeScript code that communicates with the master. Avoid hardcoding non-master URLs; centralize the master address (e.g., via config or env) when possible, and ensure internal service communication uses localhost:3000 in this architecture.

Applied to files:

  • src/core/worker/WorkerMessages.ts
  • src/core/configuration/Config.ts
  • src/core/configuration/PreprodConfig.ts
  • src/core/configuration/DefaultConfig.ts
  • src/client/HostLobbyModal.ts
  • src/core/worker/Worker.worker.ts
  • src/client/graphics/layers/MultiTabModal.ts
  • src/core/configuration/Env.ts
  • src/server/Archive.ts
  • src/server/WorkerMetrics.ts
  • vite.config.ts
  • src/server/OtelResource.ts
  • tests/pathfinding/utils.ts
  • src/core/configuration/DevConfig.ts
  • src/client/JoinPrivateLobbyModal.ts
  • src/core/configuration/ConfigLoader.ts
📚 Learning: 2025-11-12T23:11:34.445Z
Learnt from: MaxHT0x
Repo: openfrontio/OpenFrontIO PR: 2262
File: src/core/configuration/DefaultConfig.ts:806-832
Timestamp: 2025-11-12T23:11:34.445Z
Learning: In src/core/configuration/DefaultConfig.ts, JSDoc documentation for configuration methods should not be added inline, as it was requested that documentation be placed elsewhere in the codebase.

Applied to files:

  • src/core/configuration/DefaultConfig.ts
  • src/core/configuration/DevConfig.ts
  • src/core/configuration/ConfigLoader.ts
📚 Learning: 2025-06-09T02:20:43.637Z
Learnt from: VariableVince
Repo: openfrontio/OpenFrontIO PR: 1110
File: src/client/Main.ts:293-295
Timestamp: 2025-06-09T02:20:43.637Z
Learning: In src/client/Main.ts, during game start in the handleJoinLobby callback, UI elements are hidden using direct DOM manipulation with classList.add("hidden") for consistency. This includes modals, buttons, and error divs. The codebase follows this pattern rather than using component APIs for hiding elements during game transitions.

Applied to files:

  • src/client/HostLobbyModal.ts
  • src/client/JoinPrivateLobbyModal.ts
📚 Learning: 2026-01-02T18:11:06.832Z
Learnt from: ryanbarlow97
Repo: openfrontio/OpenFrontIO PR: 2740
File: src/client/HostLobbyModal.ts:821-821
Timestamp: 2026-01-02T18:11:06.832Z
Learning: In src/client/HostLobbyModal.ts, the ?s=xxxxx URL suffix used in lobby URLs is purely for cache-busting platform previews (e.g., Discord, WhatsApp, x.com) and is not used by the join logic. The join flow ignores the suffix value, so regenerating it via updateUrlWithSuffix() on configuration changes will not break existing shared URLs; it only prompts platforms to refresh preview metadata. Treat the suffix as non-functional for join behavior and ensure any related changes preserve that invariant.

Applied to files:

  • src/client/HostLobbyModal.ts
📚 Learning: 2025-10-21T20:06:04.823Z
Learnt from: Saphereye
Repo: openfrontio/OpenFrontIO PR: 2233
File: src/client/HostLobbyModal.ts:891-891
Timestamp: 2025-10-21T20:06:04.823Z
Learning: For the HumansVsNations game mode in `src/client/HostLobbyModal.ts` and related files, the implementation strategy is to generate all nations and adjust their strength for balancing, rather than limiting lobby size based on the number of available nations on the map.

Applied to files:

  • src/client/HostLobbyModal.ts
📚 Learning: 2025-12-26T22:21:21.904Z
Learnt from: FloPinguin
Repo: openfrontio/OpenFrontIO PR: 2689
File: src/client/PublicLobby.ts:245-245
Timestamp: 2025-12-26T22:21:21.904Z
Learning: In public lobbies with HumansVsNations mode in src/client/PublicLobby.ts, maxPlayers represents only human player slots (already halved in DefaultConfig.ts). The nation NPCs are added automatically server-side and don't count toward maxPlayers. Therefore, getTeamSize correctly returns maxPlayers directly for HumansVsNations to display the proper team size (e.g., maxPlayers=5 yields "5 Humans vs 5 Nations").

Applied to files:

  • src/client/HostLobbyModal.ts
  • src/client/JoinPrivateLobbyModal.ts
📚 Learning: 2025-08-12T00:31:50.144Z
Learnt from: scottanderson
Repo: openfrontio/OpenFrontIO PR: 1752
File: src/core/game/Game.ts:750-752
Timestamp: 2025-08-12T00:31:50.144Z
Learning: In the OpenFrontIO codebase, changes to the PlayerInteraction interface (like adding canDonateGold and canDonateTroops flags) do not require corresponding updates to src/core/Schemas.ts or server serialization code.

Applied to files:

  • src/client/HostLobbyModal.ts
  • src/core/configuration/DevConfig.ts
📚 Learning: 2025-06-16T03:03:59.778Z
Learnt from: VariableVince
Repo: openfrontio/OpenFrontIO PR: 1192
File: src/client/graphics/layers/RadialMenuElements.ts:312-314
Timestamp: 2025-06-16T03:03:59.778Z
Learning: In RadialMenuElements.ts, when fixing undefined params errors in subMenu functions, use explicit checks like `if (params === undefined || params.selected === null)` rather than optional chaining, as it makes the intent clearer and matches the specific error scenarios where params can be undefined during spawn phase operations.

Applied to files:

  • src/core/configuration/Env.ts
📚 Learning: 2025-12-13T14:58:29.645Z
Learnt from: scamiv
Repo: openfrontio/OpenFrontIO PR: 2607
File: src/core/execution/PlayerExecution.ts:271-295
Timestamp: 2025-12-13T14:58:29.645Z
Learning: In src/core/execution/PlayerExecution.ts surroundedBySamePlayer(), the `as Player` cast on `mg.playerBySmallID(scan.enemyId)` is intentional. Since scan.enemyId comes from ownerID() on an owned tile and playerBySmallID() only returns Player or undefined, the cast expresses a known invariant. The maintainers prefer loud failures (runtime errors) over silent masking (early returns with guards) for corrupted game state scenarios at trusted call sites.

Applied to files:

  • src/core/configuration/Env.ts
📚 Learning: 2025-08-09T05:03:18.745Z
Learnt from: scottanderson
Repo: openfrontio/OpenFrontIO PR: 1761
File: src/core/ApiSchemas.ts:2-2
Timestamp: 2025-08-09T05:03:18.745Z
Learning: After migrating to Zod v4 in the OpenFrontIO project, the standard import statement `import { z } from "zod"` should be used across the entire codebase, not `import { z } from "zod/v4"`.

Applied to files:

  • src/server/Archive.ts
📚 Learning: 2025-10-20T20:15:28.858Z
Learnt from: sambokai
Repo: openfrontio/OpenFrontIO PR: 2225
File: src/core/execution/FakeHumanExecution.ts:51-51
Timestamp: 2025-10-20T20:15:28.858Z
Learning: In src/core/execution/FakeHumanExecution.ts, game balance constants like MIRV_COOLDOWN_TICKS, MIRV_HESITATION_ODDS, VICTORY_DENIAL_TEAM_THRESHOLD, VICTORY_DENIAL_INDIVIDUAL_THRESHOLD, and STEAMROLL_CITY_GAP_MULTIPLIER are experimental tuning parameters subject to frequent change during balance testing. Do not flag changes to these values as issues or compare them against previous values.

Applied to files:

  • vite.config.ts
📚 Learning: 2026-01-02T18:11:15.132Z
Learnt from: ryanbarlow97
Repo: openfrontio/OpenFrontIO PR: 2740
File: src/client/HostLobbyModal.ts:821-821
Timestamp: 2026-01-02T18:11:15.132Z
Learning: In src/client/HostLobbyModal.ts, the `?s=xxxxx` URL suffix in lobby URLs is purely for cache-busting embed previews on platforms like Discord, WhatsApp, and x.com. The suffix value is ignored by the join logic (any value works), so regenerating it on config changes via `updateUrlWithSuffix()` doesn't break existing shared URLs - it only forces platforms to re-fetch updated preview metadata.

Applied to files:

  • src/client/JoinPrivateLobbyModal.ts
🧬 Code graph analysis (8)
src/client/HostLobbyModal.ts (1)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
src/server/Archive.ts (1)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
src/server/WorkerMetrics.ts (2)
src/server/GameManager.ts (1)
  • GameManager (15-136)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
vite.config.ts (3)
src/core/configuration/DefaultConfig.ts (1)
  • env (253-255)
src/core/configuration/DevConfig.ts (1)
  • env (23-25)
src/core/configuration/PreprodConfig.ts (1)
  • env (5-7)
src/server/OtelResource.ts (1)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
src/core/configuration/DevConfig.ts (1)
src/core/configuration/DefaultConfig.ts (1)
  • isReplay (261-263)
src/client/JoinPrivateLobbyModal.ts (2)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
src/core/configuration/Env.ts (1)
  • Env (58-133)
src/core/configuration/ConfigLoader.ts (6)
src/core/configuration/DefaultConfig.ts (4)
  • gameConfig (278-280)
  • userSettings (282-287)
  • isReplay (261-263)
  • DefaultConfig (243-1000)
src/core/Schemas.ts (1)
  • GameConfig (90-90)
src/core/configuration/Config.ts (2)
  • Config (70-181)
  • ServerConfig (29-63)
src/core/configuration/DevConfig.ts (2)
  • DevConfig (50-59)
  • DevServerConfig (6-48)
src/core/configuration/Env.ts (1)
  • Env (58-133)
src/core/configuration/PreprodConfig.ts (1)
  • preprodConfig (4-26)
🔇 Additional comments (17)
.vscode/launch.json (1)

24-26: LGTM!

Environment value now matches the new GameEnv.Dev enum convention used throughout the codebase.

src/client/graphics/layers/MultiTabModal.ts (1)

30-37: LGTM!

Clean migration from server config to game config for environment access. The early return for dev mode, spawn phase, and singleplayer games is clear and correct.

package.json (1)

8-11: LGTM!

Scripts consistently use GAME_ENV=Dev for local development. The dev:staging and dev:prod variants correctly differentiate only by API_DOMAIN - running locally in dev mode but hitting remote APIs.

src/core/worker/WorkerMessages.ts (1)

1-1: LGTM!

Clean addition of GameEnv import and the env property to InitMessage. This properly types the environment information passed to workers during initialization.

Also applies to: 40-45

src/server/WorkerMetrics.ts (1)

7-7: LGTM!

Straightforward migration from the old getServerConfigFromServer() to the new getServerConfig() API. Consistent with the broader config refactor.

Also applies to: 15-15

src/server/OtelResource.ts (1)

8-8: Module-level config initialization runs at import time.

getServerConfig() is called when this module is first imported. This means the console log ("using dev/preprod/prod server config") fires immediately at module load. This should be fine for server-side code, but verify that Env.GAME_ENV is always set before this module is imported.

src/core/worker/Worker.worker.ts (1)

44-56: LGTM! The env parameter is properly threaded through.

The message.env from the InitMessage is correctly passed as the second parameter to createGameRunner. This completes the environment propagation from main thread to worker.

src/core/configuration/Config.ts (2)

23-27: Clear naming improvement.

Renaming Preprod to Staging is more standard terminology. The enum is clean and simple.


70-71: Good addition to the interface.

Adding env(): GameEnv to Config allows access to the environment from game configuration, matching the pattern already in ServerConfig. This keeps the API consistent.

src/server/Archive.ts (1)

2-2: Import and usage updated correctly.

The migration from getServerConfigFromServer to getServerConfig is applied consistently. Module-level initialization is appropriate for server code.

Also applies to: 13-13

src/client/JoinPrivateLobbyModal.ts (1)

572-572: Clean simplification using Env.GIT_COMMIT.

Direct access to Env.GIT_COMMIT is simpler than the previous async fetch approach. This removes the need for Promise.all handling.

src/client/HostLobbyModal.ts (1)

4-4: Import and usage look correct.

The migration to getServerConfig() is applied correctly across all call sites.

Also applies to: 1159-1159

tests/pathfinding/utils.ts (2)

3-3: Import added correctly.

Direct import of GameEnv from the configuration module is clean and explicit.


251-271: Test config setup simplified.

Passing GameEnv.Dev directly to TestConfig is cleaner than the previous approach with TestServerConfig. The signature (configObject, GameEnv.Dev, UserSettings, boolean) is clear and easy to understand.

src/core/configuration/DefaultConfig.ts (1)

246-255: Clean refactor using composition.

The constructor now takes GameEnv directly rather than depending on a ServerConfig object. This is simpler and clearer. The env() accessor provides direct access to the injected value.

src/core/configuration/DevConfig.ts (1)

50-58: LGTM!

The DevConfig constructor update aligns with the parent class signature change. Simple pass-through to super().

src/core/configuration/ConfigLoader.ts (1)

28-43: LGTM!

The new getServerConfig() function provides a clean factory for ServerConfig based on Env.GAME_ENV. The console logs here are reasonable since this runs once during initialization.

GHCR_REPO: ${{ vars.GHCR_REPO }}
GHCR_USERNAME: ${{ vars.GHCR_USERNAME }}
ENV: ${{ inputs.target_domain == 'openfront.io' && 'prod' || 'staging' }}
ENV: ${{ inputs.target_domain == 'openfront.io' && 'Prod' || 'Staging' }}
Copy link
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:

# Locate the build-deploy.sh script
fd -type f -name "build-deploy.sh"

Repository: openfrontio/OpenFrontIO

Length of output: 237


🏁 Script executed:

# Find build-deploy.sh anywhere in the repository
find . -name "build-deploy.sh" 2>/dev/null

Repository: openfrontio/OpenFrontIO

Length of output: 84


🏁 Script executed:

# Read the build-deploy.sh script
cat -n ./build-deploy.sh

Repository: openfrontio/OpenFrontIO

Length of output: 3069


🏁 Script executed:

# Check build.sh for ENV validation
head -50 ./build.sh | grep -n -A 5 -B 5 "prod\|staging" || echo "Searching full file..."
cat -n ./build.sh | grep -n "prod\|staging" | head -20

Repository: openfrontio/OpenFrontIO

Length of output: 1119


🏁 Script executed:

# Check deploy.sh for ENV validation
cat -n ./deploy.sh | grep -n "prod\|staging" | head -20

Repository: openfrontio/OpenFrontIO

Length of output: 765


Fix capitalization mismatch: use lowercase 'prod' and 'staging' in ENV value.

The deploy.yml change at line 113 sends capitalized 'Prod' and 'Staging' values, but build-deploy.sh (line 29), build.sh (line 30), and deploy.sh (line 24) all validate the ENV argument with strict lowercase checks (prod and staging). The scripts will reject the capitalized values and fail. Change ENV to use lowercase to match script expectations.

🤖 Prompt for AI Agents
In @.github/workflows/deploy.yml at line 113, The ENV value in the workflow
currently emits capitalized 'Prod'/'Staging', which will be rejected by the
downstream scripts; change the GitHub Actions expression that sets ENV so it
outputs lowercase 'prod' and 'staging' instead of 'Prod'/'Staging' (i.e., update
the ENV assignment expression that uses inputs.target_domain to return 'prod' or
'staging'), ensuring consistency with the strict lowercase checks in
build-deploy.sh, build.sh, and deploy.sh.

Comment on lines 23 to 25
default:
throw Error(`unsupported server configuration: ${Env.GAME_ENV}`);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Error message references wrong variable.

The switch operates on the gameEnv parameter, but the error message references Env.GAME_ENV. If the default case is reached, the logged value may differ from the actual argument that caused the error.

Proposed fix
     default:
-      throw Error(`unsupported server configuration: ${Env.GAME_ENV}`);
+      throw Error(`unsupported server configuration: ${gameEnv}`);
   }
🤖 Prompt for AI Agents
In @src/core/configuration/ConfigLoader.ts around lines 23 - 25, The default
case in the switch inside ConfigLoader.ts throws an error referencing
Env.GAME_ENV instead of the actual function parameter; update the throw in that
default branch to include the incoming gameEnv variable (the parameter named
gameEnv) so the error message reflects the actual argument that triggered the
unsupported configuration, e.g. replace Env.GAME_ENV with gameEnv in the thrown
Error in the default case of the switch.

Comment on lines 59 to 99
get GAME_ENV(): GameEnv {
// Check window.SERVER_CONFIG first (injected at build time)
try {
if (typeof window !== "undefined" && window.SERVER_CONFIG) {
console.log("using server config from window");
return GameEnv[window.SERVER_CONFIG.gameEnv as keyof typeof GameEnv];
}
} catch {
// Ignore errors accessing window
}

// Check MODE for Vite, GAME_ENV for Node
try {
if (
typeof import.meta !== "undefined" &&
import.meta.env &&
import.meta.env.MODE
) {
return import.meta.env.MODE;
console.log("using server config from import.meta.env");
return GameEnv[import.meta.env.MODE as keyof typeof GameEnv];
}
} catch {
// Ignore errors accessing import.meta
}

return getEnv("GAME_ENV") ?? "dev";
console.log("using server config from environment variable");
return GameEnv[getEnv("GAME_ENV") as keyof typeof GameEnv];
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing validation for GameEnv lookup can return undefined.

When the string from window.SERVER_CONFIG.gameEnv, import.meta.env.MODE, or getEnv("GAME_ENV") does not match a valid GameEnv key, the expression GameEnv[... as keyof typeof GameEnv] returns undefined. The return type says GameEnv, but callers may get undefined at runtime.

Consider adding validation:

Proposed fix
 get GAME_ENV(): GameEnv {
+  const toGameEnv = (value: string | undefined): GameEnv => {
+    if (value && value in GameEnv) {
+      return GameEnv[value as keyof typeof GameEnv];
+    }
+    throw new Error(`Invalid GAME_ENV value: ${value}`);
+  };
+
   // Check window.SERVER_CONFIG first (injected at build time)
   try {
     if (typeof window !== "undefined" && window.SERVER_CONFIG) {
       console.log("using server config from window");
-      return GameEnv[window.SERVER_CONFIG.gameEnv as keyof typeof GameEnv];
+      return toGameEnv(window.SERVER_CONFIG.gameEnv);
     }
   } catch {
     // Ignore errors accessing window
   }

   // Check MODE for Vite, GAME_ENV for Node
   try {
     if (
       typeof import.meta !== "undefined" &&
       import.meta.env &&
       import.meta.env.MODE
     ) {
       console.log("using server config from import.meta.env");
-      return GameEnv[import.meta.env.MODE as keyof typeof GameEnv];
+      return toGameEnv(import.meta.env.MODE);
     }
   } catch {
     // Ignore errors accessing import.meta
   }
   console.log("using server config from environment variable");
-  return GameEnv[getEnv("GAME_ENV") as keyof typeof GameEnv];
+  return toGameEnv(getEnv("GAME_ENV"));
 },
📝 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
get GAME_ENV(): GameEnv {
// Check window.SERVER_CONFIG first (injected at build time)
try {
if (typeof window !== "undefined" && window.SERVER_CONFIG) {
console.log("using server config from window");
return GameEnv[window.SERVER_CONFIG.gameEnv as keyof typeof GameEnv];
}
} catch {
// Ignore errors accessing window
}
// Check MODE for Vite, GAME_ENV for Node
try {
if (
typeof import.meta !== "undefined" &&
import.meta.env &&
import.meta.env.MODE
) {
return import.meta.env.MODE;
console.log("using server config from import.meta.env");
return GameEnv[import.meta.env.MODE as keyof typeof GameEnv];
}
} catch {
// Ignore errors accessing import.meta
}
return getEnv("GAME_ENV") ?? "dev";
console.log("using server config from environment variable");
return GameEnv[getEnv("GAME_ENV") as keyof typeof GameEnv];
},
get GAME_ENV(): GameEnv {
const toGameEnv = (value: string | undefined): GameEnv => {
if (value && value in GameEnv) {
return GameEnv[value as keyof typeof GameEnv];
}
throw new Error(`Invalid GAME_ENV value: ${value}`);
};
// Check window.SERVER_CONFIG first (injected at build time)
try {
if (typeof window !== "undefined" && window.SERVER_CONFIG) {
console.log("using server config from window");
return toGameEnv(window.SERVER_CONFIG.gameEnv);
}
} catch {
// Ignore errors accessing window
}
// Check MODE for Vite, GAME_ENV for Node
try {
if (
typeof import.meta !== "undefined" &&
import.meta.env &&
import.meta.env.MODE
) {
console.log("using server config from import.meta.env");
return toGameEnv(import.meta.env.MODE);
}
} catch {
// Ignore errors accessing import.meta
}
console.log("using server config from environment variable");
return toGameEnv(getEnv("GAME_ENV"));
},
🤖 Prompt for AI Agents
In @src/core/configuration/Env.ts around lines 59 - 85, The GAME_ENV getter can
return undefined when mapping strings to the GameEnv enum; validate each lookup
(for window.SERVER_CONFIG.gameEnv, import.meta.env.MODE, and getEnv("GAME_ENV"))
by checking that the mapped value is a valid GameEnv member before returning it,
and otherwise fall back to a safe default or throw a clear error; update the get
GAME_ENV implementation to compute the candidate key, verify via something like
Object.hasOwnProperty or a reverse-lookup on GameEnv, and only return
GameEnv[candidate] when valid, otherwise continue to the next source or return a
validated default.

Copy link
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: 3

Caution

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

⚠️ Outside diff range comments (1)
src/core/configuration/ConfigLoader.ts (1)

28-43: LGTM!

The getServerConfig function is clean and straightforward. Good use of a switch on the enum. The console logs are helpful for debugging which config is active.

One small thing: the log message says "preprod server config" for GameEnv.Staging (line 35). Update it to "using staging server config" to match the enum name for consistency.

Verified: all old function references (getServerConfigFromClient / getServerConfigFromServer) have been removed, and the new getServerConfig is being used correctly across the codebase.

🤖 Fix all issues with AI agents
In @src/core/configuration/ConfigLoader.ts:
- Around line 10-26: The thrown error in getConfig incorrectly references
Env.GAME_ENV instead of the function parameter; update the error to include the
provided gameEnv parameter (e.g., throw Error(`unsupported server configuration:
${gameEnv}`)) so the message reflects the actual value passed into getConfig and
aids debugging of GameEnv handling.

In @src/core/configuration/Env.ts:
- Around line 58-70: The error handling in getGameEnvValue is inconsistent: it
throws when envString is undefined but silently returns GameEnv.Dev for
unrecognized strings; make it strict by throwing for both cases — update
getGameEnvValue to throw a clear Error when envString is undefined or when the
normalized value does not match any GameEnv key (include the provided envString
in the error message for debugging) instead of defaulting to GameEnv.Dev so
callers must supply a valid GAME_ENV.

In @vite.config.ts:
- Around line 59-63: The numWorkers value inside serverConfig can become NaN
because parseInt(env.NUM_WORKERS, 10) returns NaN for missing/invalid values;
change the expression used for serverConfig.numWorkers to parse and validate the
env var and fall back to a safe default (e.g., 2) in production—e.g., compute a
parsed value (parseInt(..., 10)), check Number.isNaN(parsed) (or isFinite) and
use the fallback if invalid, leaving the non-production branch as 2; update the
numWorkers expression inside the serverConfig JSON.stringify accordingly.
🧹 Nitpick comments (4)
vite.config.ts (1)

14-25: Unused gitCommit variable is now dead code.

The gitCommit variable computed here is no longer referenced after the refactor. The new serverConfig uses env.GIT_COMMIT directly for production and "DEV" for development.

Consider removing this dead code block.

🧹 Suggested cleanup
-let gitCommit = process.env.GIT_COMMIT;
-
-if (!gitCommit) {
-  try {
-    gitCommit = execSync("git rev-parse HEAD").toString().trim();
-  } catch (error) {
-    if (process.env.NODE_ENV !== "production") {
-      console.warn("Unable to determine git commit:", error.message);
-    }
-    gitCommit = "unknown";
-  }
-}
tests/pathfinding/utils.ts (1)

3-3: Import path style is inconsistent with the rest of the file.

Other imports in this file use relative paths (e.g., "../../src/core/game/Game"), but this import uses an absolute-style path "src/core/configuration/Config". Consider using a relative path for consistency.

Suggested fix
-import { GameEnv } from "src/core/configuration/Config";
+import { GameEnv } from "../../src/core/configuration/Config";
src/core/configuration/Env.ts (2)

73-98: Remove or reduce console.log statements for production.

These log statements (lines 77, 91, 97) will run on every access to GAME_ENV. In production, this adds noise. Consider using console.debug or removing them once the refactor is stable.

Use console.debug instead
     if (typeof window !== "undefined" && window.SERVER_CONFIG) {
-        console.log("using server config from window");
+        console.debug("using server config from window");
         return getGameEnvValue(window.SERVER_CONFIG.gameEnv);
       }

Apply similar changes to lines 91 and 97.


135-146: NUM_WORKERS return type could be more precise.

The getter returns string | undefined implicitly (via getEnv), but when reading from window.SERVER_CONFIG, it converts the number to string. This works, but the return type annotation is missing. Consider adding explicit return type for clarity.

Also, if callers expect a number, they'll need to parse this. Might be cleaner to return number | undefined and parse once here.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d56fc5b and 5766d71.

📒 Files selected for processing (33)
  • .vscode/launch.json
  • .vscode/settings.json
  • index.html
  • nginx.conf
  • package.json
  • src/client/ClientGameRunner.ts
  • src/client/HostLobbyModal.ts
  • src/client/JoinPrivateLobbyModal.ts
  • src/client/Main.ts
  • src/client/Matchmaking.ts
  • src/client/graphics/layers/MultiTabModal.ts
  • src/core/GameRunner.ts
  • src/core/configuration/Config.ts
  • src/core/configuration/ConfigLoader.ts
  • src/core/configuration/DefaultConfig.ts
  • src/core/configuration/DevConfig.ts
  • src/core/configuration/Env.ts
  • src/core/configuration/PreprodConfig.ts
  • src/core/worker/Worker.worker.ts
  • src/core/worker/WorkerClient.ts
  • src/core/worker/WorkerMessages.ts
  • src/server/Archive.ts
  • src/server/Logger.ts
  • src/server/MapPlaylist.ts
  • src/server/Master.ts
  • src/server/OtelResource.ts
  • src/server/Server.ts
  • src/server/Worker.ts
  • src/server/WorkerMetrics.ts
  • tests/core/pathfinding/_fixtures.ts
  • tests/pathfinding/utils.ts
  • tests/util/Setup.ts
  • vite.config.ts
💤 Files with no reviewable changes (1)
  • nginx.conf
✅ Files skipped from review due to trivial changes (1)
  • .vscode/settings.json
🚧 Files skipped from review as they are similar to previous changes (15)
  • src/core/GameRunner.ts
  • src/core/worker/WorkerMessages.ts
  • index.html
  • src/server/MapPlaylist.ts
  • src/server/Server.ts
  • src/client/ClientGameRunner.ts
  • src/core/configuration/DefaultConfig.ts
  • src/server/OtelResource.ts
  • .vscode/launch.json
  • src/client/Main.ts
  • src/server/Logger.ts
  • tests/util/Setup.ts
  • package.json
  • src/client/graphics/layers/MultiTabModal.ts
  • src/client/HostLobbyModal.ts
🧰 Additional context used
🧠 Learnings (10)
📚 Learning: 2026-01-12T21:37:01.156Z
Learnt from: FloPinguin
Repo: openfrontio/OpenFrontIO PR: 2874
File: src/server/MapLandTiles.ts:7-11
Timestamp: 2026-01-12T21:37:01.156Z
Learning: In this repository's OpenFrontIO deployment, inter-service HTTP calls to the master should target http://localhost:3000 (master at port 3000) as the canonical address. Apply this as the standard for all server-side TypeScript code that communicates with the master. Avoid hardcoding non-master URLs; centralize the master address (e.g., via config or env) when possible, and ensure internal service communication uses localhost:3000 in this architecture.

Applied to files:

  • src/core/configuration/PreprodConfig.ts
  • src/client/Matchmaking.ts
  • src/server/WorkerMetrics.ts
  • tests/core/pathfinding/_fixtures.ts
  • src/server/Archive.ts
  • tests/pathfinding/utils.ts
  • src/server/Worker.ts
  • src/core/configuration/Env.ts
  • vite.config.ts
  • src/core/configuration/Config.ts
  • src/core/worker/WorkerClient.ts
  • src/server/Master.ts
  • src/core/worker/Worker.worker.ts
  • src/core/configuration/DevConfig.ts
  • src/core/configuration/ConfigLoader.ts
  • src/client/JoinPrivateLobbyModal.ts
📚 Learning: 2025-10-21T20:06:04.823Z
Learnt from: Saphereye
Repo: openfrontio/OpenFrontIO PR: 2233
File: src/client/HostLobbyModal.ts:891-891
Timestamp: 2025-10-21T20:06:04.823Z
Learning: For the HumansVsNations game mode in `src/client/HostLobbyModal.ts` and related files, the implementation strategy is to generate all nations and adjust their strength for balancing, rather than limiting lobby size based on the number of available nations on the map.

Applied to files:

  • tests/core/pathfinding/_fixtures.ts
📚 Learning: 2025-08-09T05:03:18.745Z
Learnt from: scottanderson
Repo: openfrontio/OpenFrontIO PR: 1761
File: src/core/ApiSchemas.ts:2-2
Timestamp: 2025-08-09T05:03:18.745Z
Learning: After migrating to Zod v4 in the OpenFrontIO project, the standard import statement `import { z } from "zod"` should be used across the entire codebase, not `import { z } from "zod/v4"`.

Applied to files:

  • src/server/Archive.ts
📚 Learning: 2025-06-16T03:03:59.778Z
Learnt from: VariableVince
Repo: openfrontio/OpenFrontIO PR: 1192
File: src/client/graphics/layers/RadialMenuElements.ts:312-314
Timestamp: 2025-06-16T03:03:59.778Z
Learning: In RadialMenuElements.ts, when fixing undefined params errors in subMenu functions, use explicit checks like `if (params === undefined || params.selected === null)` rather than optional chaining, as it makes the intent clearer and matches the specific error scenarios where params can be undefined during spawn phase operations.

Applied to files:

  • src/core/configuration/Env.ts
📚 Learning: 2025-12-13T14:58:29.645Z
Learnt from: scamiv
Repo: openfrontio/OpenFrontIO PR: 2607
File: src/core/execution/PlayerExecution.ts:271-295
Timestamp: 2025-12-13T14:58:29.645Z
Learning: In src/core/execution/PlayerExecution.ts surroundedBySamePlayer(), the `as Player` cast on `mg.playerBySmallID(scan.enemyId)` is intentional. Since scan.enemyId comes from ownerID() on an owned tile and playerBySmallID() only returns Player or undefined, the cast expresses a known invariant. The maintainers prefer loud failures (runtime errors) over silent masking (early returns with guards) for corrupted game state scenarios at trusted call sites.

Applied to files:

  • src/core/configuration/Env.ts
📚 Learning: 2025-11-12T23:11:34.445Z
Learnt from: MaxHT0x
Repo: openfrontio/OpenFrontIO PR: 2262
File: src/core/configuration/DefaultConfig.ts:806-832
Timestamp: 2025-11-12T23:11:34.445Z
Learning: In src/core/configuration/DefaultConfig.ts, JSDoc documentation for configuration methods should not be added inline, as it was requested that documentation be placed elsewhere in the codebase.

Applied to files:

  • src/core/configuration/DevConfig.ts
  • src/core/configuration/ConfigLoader.ts
📚 Learning: 2025-08-12T00:31:50.144Z
Learnt from: scottanderson
Repo: openfrontio/OpenFrontIO PR: 1752
File: src/core/game/Game.ts:750-752
Timestamp: 2025-08-12T00:31:50.144Z
Learning: In the OpenFrontIO codebase, changes to the PlayerInteraction interface (like adding canDonateGold and canDonateTroops flags) do not require corresponding updates to src/core/Schemas.ts or server serialization code.

Applied to files:

  • src/core/configuration/DevConfig.ts
📚 Learning: 2025-06-09T02:20:43.637Z
Learnt from: VariableVince
Repo: openfrontio/OpenFrontIO PR: 1110
File: src/client/Main.ts:293-295
Timestamp: 2025-06-09T02:20:43.637Z
Learning: In src/client/Main.ts, during game start in the handleJoinLobby callback, UI elements are hidden using direct DOM manipulation with classList.add("hidden") for consistency. This includes modals, buttons, and error divs. The codebase follows this pattern rather than using component APIs for hiding elements during game transitions.

Applied to files:

  • src/client/JoinPrivateLobbyModal.ts
📚 Learning: 2026-01-02T18:11:15.132Z
Learnt from: ryanbarlow97
Repo: openfrontio/OpenFrontIO PR: 2740
File: src/client/HostLobbyModal.ts:821-821
Timestamp: 2026-01-02T18:11:15.132Z
Learning: In src/client/HostLobbyModal.ts, the `?s=xxxxx` URL suffix in lobby URLs is purely for cache-busting embed previews on platforms like Discord, WhatsApp, and x.com. The suffix value is ignored by the join logic (any value works), so regenerating it on config changes via `updateUrlWithSuffix()` doesn't break existing shared URLs - it only forces platforms to re-fetch updated preview metadata.

Applied to files:

  • src/client/JoinPrivateLobbyModal.ts
📚 Learning: 2025-12-26T22:21:21.904Z
Learnt from: FloPinguin
Repo: openfrontio/OpenFrontIO PR: 2689
File: src/client/PublicLobby.ts:245-245
Timestamp: 2025-12-26T22:21:21.904Z
Learning: In public lobbies with HumansVsNations mode in src/client/PublicLobby.ts, maxPlayers represents only human player slots (already halved in DefaultConfig.ts). The nation NPCs are added automatically server-side and don't count toward maxPlayers. Therefore, getTeamSize correctly returns maxPlayers directly for HumansVsNations to display the proper team size (e.g., maxPlayers=5 yields "5 Humans vs 5 Nations").

Applied to files:

  • src/client/JoinPrivateLobbyModal.ts
🧬 Code graph analysis (7)
src/client/Matchmaking.ts (1)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
src/server/WorkerMetrics.ts (1)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
src/server/Archive.ts (1)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
src/server/Worker.ts (3)
src/core/game/GameImpl.ts (1)
  • config (357-359)
src/core/game/GameView.ts (1)
  • config (783-785)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
vite.config.ts (3)
src/core/configuration/DefaultConfig.ts (1)
  • env (253-255)
src/core/configuration/DevConfig.ts (1)
  • env (23-25)
src/core/configuration/PreprodConfig.ts (1)
  • env (5-7)
src/core/worker/WorkerClient.ts (3)
src/core/configuration/DefaultConfig.ts (1)
  • env (253-255)
src/core/configuration/DevConfig.ts (1)
  • env (23-25)
src/core/configuration/PreprodConfig.ts (1)
  • env (5-7)
src/server/Master.ts (1)
src/core/configuration/ConfigLoader.ts (1)
  • getServerConfig (28-43)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Deploy to openfront.dev
🔇 Additional comments (20)
src/server/WorkerMetrics.ts (1)

7-15: LGTM!

Clean update to use the new unified getServerConfig() function. The import and call site are correctly updated.

src/core/worker/WorkerClient.ts (2)

23-27: LGTM!

Good use of composition - passing GameEnv explicitly to the worker rather than relying on global state or inheritance. The constructor signature change is clean and typed.


72-78: LGTM!

The env field is correctly included in the init message payload, matching the updated InitMessage schema in WorkerMessages.ts.

src/server/Worker.ts (1)

10-33: LGTM!

Clean migration to getServerConfig(). The import and call site are correctly updated.

One thing to keep in mind: getServerConfig() is called at module load time (line 33), so ensure Env.GAME_ENV is set before this module is imported. Based on the codebase pattern where dotenv.config() is called early in the startup sequence, this should be fine.

src/core/configuration/PreprodConfig.ts (1)

4-7: LGTM!

Correctly updated to use GameEnv.Staging matching the renamed enum value in Config.ts.

src/client/Matchmaking.ts (1)

4-4: LGTM on config retrieval refactor.

The switch from getServerConfigFromClient() to the synchronous getServerConfig() aligns with the broader refactor. Removing the await is correct since getServerConfig() now returns ServerConfig directly.

Also applies to: 146-146, 225-225

src/core/worker/Worker.worker.ts (1)

42-56: LGTM on env parameter propagation.

The updated call to createGameRunner correctly passes message.env as the second argument, matching the new signature. The InitMessage type (per the AI summary) now includes the env: GameEnv field.

src/core/configuration/Config.ts (2)

70-71: Good addition of env() to the Config interface.

Adding env(): GameEnv to the Config interface provides a clean way for components to access the current environment without depending on ServerConfig.


23-27: Enum rename from Preprod to Staging is complete.

All references to GameEnv.Preprod have been updated to GameEnv.Staging. The enum definition is correct, and the ConfigLoader and PreprodConfig classes both use the new enum value properly. No leftover references to the old name exist.

src/server/Archive.ts (1)

2-2: LGTM on config retrieval update.

The switch to getServerConfig() is correct. Top-level initialization is appropriate for server-side module code.

Also applies to: 13-13

src/server/Master.ts (1)

9-9: LGTM on config retrieval update.

The switch to getServerConfig() is correct. The removal of the /api/env endpoint (per the AI summary) aligns with the new approach of injecting server config into the client via window.SERVER_CONFIG.

Also applies to: 15-15

src/client/JoinPrivateLobbyModal.ts (3)

3-3: LGTM on import updates.

Adding Env import and switching to getServerConfig() aligns with the refactor.

Also applies to: 12-12


549-582: Good simplification of checkArchivedGame.

Replacing the parallel gitCommitPromise fetch with direct Env.GIT_COMMIT access removes unnecessary complexity. The version check logic remains intact.


514-516: LGTM on synchronous config access.

Both checkActiveLobby and pollPlayers now use the synchronous getServerConfig() correctly.

Also applies to: 598-603

tests/core/pathfinding/_fixtures.ts (1)

3-3: Clean refactoring of test config initialization.

The switch from TestServerConfig to GameEnv.Dev aligns with the new DefaultConfig constructor that takes GameEnv as the second parameter. Using GameEnv.Dev for tests is the right choice.

tests/pathfinding/utils.ts (1)

251-271: LGTM!

The TestConfig constructor call correctly passes GameEnv.Dev as the second argument, aligning with the new config API that takes the environment enum directly.

src/core/configuration/DevConfig.ts (2)

3-3: LGTM!

Clean import change - now only imports what's needed (GameEnv) instead of the full ServerConfig.


50-58: LGTM!

The constructor signature change is a good simplification. Passing GameEnv directly instead of the full ServerConfig makes the dependency clearer and the API simpler. Good use of composition - the config only receives what it actually needs.

src/core/configuration/Env.ts (2)

9-13: LGTM!

Clean interface definition. Using a simple interface for the config shape is good - typed and easy to understand.


22-24: LGTM!

Good global type augmentation for the injected server config.

Comment on lines +10 to 26
export function getConfig(
gameConfig: GameConfig,
gameEnv: GameEnv,
userSettings: UserSettings | null,
isReplay: boolean = false,
): Promise<Config> {
const sc = await getServerConfigFromClient();
switch (sc.env()) {
): Config {
switch (gameEnv) {
case GameEnv.Dev:
return new DevConfig(sc, gameConfig, userSettings, isReplay);
case GameEnv.Preprod:
return new DevConfig(gameConfig, gameEnv, userSettings, isReplay);
case GameEnv.Staging:
case GameEnv.Prod:
console.log("using prod config");
return new DefaultConfig(sc, gameConfig, userSettings, isReplay);
return new DefaultConfig(gameConfig, gameEnv, userSettings, isReplay);
default:
throw Error(`unsupported server configuration: ${Env.GAME_ENV}`);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Error message uses wrong variable.

Line 24 references Env.GAME_ENV but the function receives gameEnv as a parameter. The error message should use the parameter value for accurate debugging.

Suggested fix
     default:
-      throw Error(`unsupported server configuration: ${Env.GAME_ENV}`);
+      throw Error(`unsupported server configuration: ${gameEnv}`);
   }
📝 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
export function getConfig(
gameConfig: GameConfig,
gameEnv: GameEnv,
userSettings: UserSettings | null,
isReplay: boolean = false,
): Promise<Config> {
const sc = await getServerConfigFromClient();
switch (sc.env()) {
): Config {
switch (gameEnv) {
case GameEnv.Dev:
return new DevConfig(sc, gameConfig, userSettings, isReplay);
case GameEnv.Preprod:
return new DevConfig(gameConfig, gameEnv, userSettings, isReplay);
case GameEnv.Staging:
case GameEnv.Prod:
console.log("using prod config");
return new DefaultConfig(sc, gameConfig, userSettings, isReplay);
return new DefaultConfig(gameConfig, gameEnv, userSettings, isReplay);
default:
throw Error(`unsupported server configuration: ${Env.GAME_ENV}`);
}
}
export function getConfig(
gameConfig: GameConfig,
gameEnv: GameEnv,
userSettings: UserSettings | null,
isReplay: boolean = false,
): Config {
switch (gameEnv) {
case GameEnv.Dev:
return new DevConfig(gameConfig, gameEnv, userSettings, isReplay);
case GameEnv.Staging:
case GameEnv.Prod:
console.log("using prod config");
return new DefaultConfig(gameConfig, gameEnv, userSettings, isReplay);
default:
throw Error(`unsupported server configuration: ${gameEnv}`);
}
}
🤖 Prompt for AI Agents
In @src/core/configuration/ConfigLoader.ts around lines 10 - 26, The thrown
error in getConfig incorrectly references Env.GAME_ENV instead of the function
parameter; update the error to include the provided gameEnv parameter (e.g.,
throw Error(`unsupported server configuration: ${gameEnv}`)) so the message
reflects the actual value passed into getConfig and aids debugging of GameEnv
handling.

Comment on lines +58 to +70
// Helper function to get GameEnv value case-insensitively
function getGameEnvValue(envString: string | undefined): GameEnv {
if (!envString) {
throw new Error("GAME_ENV is not defined");
}

const normalizedEnv = envString.toLowerCase();
const enumKey = Object.keys(GameEnv).find(
(key) => key.toLowerCase() === normalizedEnv,
);

return enumKey ? GameEnv[enumKey as keyof typeof GameEnv] : GameEnv.Dev;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent error handling in getGameEnvValue.

The function throws if envString is undefined (line 61), but silently defaults to GameEnv.Dev if the string doesn't match any enum key (line 69). This is inconsistent - either both cases should throw, or both should have a default.

If defaulting to Dev is intentional, consider removing the throw and defaulting consistently. If strict validation is needed, throw on unrecognized values too.

Option A: Always default to Dev
 function getGameEnvValue(envString: string | undefined): GameEnv {
-  if (!envString) {
-    throw new Error("GAME_ENV is not defined");
-  }
+  if (!envString) {
+    return GameEnv.Dev;
+  }

   const normalizedEnv = envString.toLowerCase();
   const enumKey = Object.keys(GameEnv).find(
     (key) => key.toLowerCase() === normalizedEnv,
   );

   return enumKey ? GameEnv[enumKey as keyof typeof GameEnv] : GameEnv.Dev;
 }
Option B: Strict validation
 function getGameEnvValue(envString: string | undefined): GameEnv {
   if (!envString) {
     throw new Error("GAME_ENV is not defined");
   }

   const normalizedEnv = envString.toLowerCase();
   const enumKey = Object.keys(GameEnv).find(
     (key) => key.toLowerCase() === normalizedEnv,
   );

-  return enumKey ? GameEnv[enumKey as keyof typeof GameEnv] : GameEnv.Dev;
+  if (!enumKey) {
+    throw new Error(`Unknown GAME_ENV value: ${envString}`);
+  }
+  return GameEnv[enumKey as keyof typeof GameEnv];
 }
📝 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
// Helper function to get GameEnv value case-insensitively
function getGameEnvValue(envString: string | undefined): GameEnv {
if (!envString) {
throw new Error("GAME_ENV is not defined");
}
const normalizedEnv = envString.toLowerCase();
const enumKey = Object.keys(GameEnv).find(
(key) => key.toLowerCase() === normalizedEnv,
);
return enumKey ? GameEnv[enumKey as keyof typeof GameEnv] : GameEnv.Dev;
}
// Helper function to get GameEnv value case-insensitively
function getGameEnvValue(envString: string | undefined): GameEnv {
if (!envString) {
return GameEnv.Dev;
}
const normalizedEnv = envString.toLowerCase();
const enumKey = Object.keys(GameEnv).find(
(key) => key.toLowerCase() === normalizedEnv,
);
return enumKey ? GameEnv[enumKey as keyof typeof GameEnv] : GameEnv.Dev;
}
Suggested change
// Helper function to get GameEnv value case-insensitively
function getGameEnvValue(envString: string | undefined): GameEnv {
if (!envString) {
throw new Error("GAME_ENV is not defined");
}
const normalizedEnv = envString.toLowerCase();
const enumKey = Object.keys(GameEnv).find(
(key) => key.toLowerCase() === normalizedEnv,
);
return enumKey ? GameEnv[enumKey as keyof typeof GameEnv] : GameEnv.Dev;
}
// Helper function to get GameEnv value case-insensitively
function getGameEnvValue(envString: string | undefined): GameEnv {
if (!envString) {
throw new Error("GAME_ENV is not defined");
}
const normalizedEnv = envString.toLowerCase();
const enumKey = Object.keys(GameEnv).find(
(key) => key.toLowerCase() === normalizedEnv,
);
if (!enumKey) {
throw new Error(`Unknown GAME_ENV value: ${envString}`);
}
return GameEnv[enumKey as keyof typeof GameEnv];
}
🤖 Prompt for AI Agents
In @src/core/configuration/Env.ts around lines 58 - 70, The error handling in
getGameEnvValue is inconsistent: it throws when envString is undefined but
silently returns GameEnv.Dev for unrecognized strings; make it strict by
throwing for both cases — update getGameEnvValue to throw a clear Error when
envString is undefined or when the normalized value does not match any GameEnv
key (include the provided envString in the error message for debugging) instead
of defaulting to GameEnv.Dev so callers must supply a valid GAME_ENV.

Comment on lines +59 to +63
serverConfig: JSON.stringify({
gameEnv: isProduction ? env.GAME_ENV : "Dev",
numWorkers: isProduction ? parseInt(env.NUM_WORKERS, 10) : 2,
gitCommit: isProduction ? env.GIT_COMMIT : "DEV",
}),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential NaN when NUM_WORKERS env var is missing or invalid in production.

parseInt(env.NUM_WORKERS, 10) returns NaN if env.NUM_WORKERS is undefined or not a valid number. This would inject NaN into the client config.

Consider adding a fallback:

🛠️ Proposed fix
 serverConfig: JSON.stringify({
   gameEnv: isProduction ? env.GAME_ENV : "Dev",
-  numWorkers: isProduction ? parseInt(env.NUM_WORKERS, 10) : 2,
+  numWorkers: isProduction ? (parseInt(env.NUM_WORKERS, 10) || 2) : 2,
   gitCommit: isProduction ? env.GIT_COMMIT : "DEV",
 }),
📝 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
serverConfig: JSON.stringify({
gameEnv: isProduction ? env.GAME_ENV : "Dev",
numWorkers: isProduction ? parseInt(env.NUM_WORKERS, 10) : 2,
gitCommit: isProduction ? env.GIT_COMMIT : "DEV",
}),
serverConfig: JSON.stringify({
gameEnv: isProduction ? env.GAME_ENV : "Dev",
numWorkers: isProduction ? (parseInt(env.NUM_WORKERS, 10) || 2) : 2,
gitCommit: isProduction ? env.GIT_COMMIT : "DEV",
}),
🤖 Prompt for AI Agents
In @vite.config.ts around lines 59 - 63, The numWorkers value inside
serverConfig can become NaN because parseInt(env.NUM_WORKERS, 10) returns NaN
for missing/invalid values; change the expression used for
serverConfig.numWorkers to parse and validate the env var and fall back to a
safe default (e.g., 2) in production—e.g., compute a parsed value (parseInt(...,
10)), check Number.isNaN(parsed) (or isFinite) and use the fallback if invalid,
leaving the non-production branch as 2; update the numWorkers expression inside
the serverConfig JSON.stringify accordingly.

@evanpelle evanpelle changed the title Refactor config Inject config in index.html, remove /env endpoint Jan 13, 2026
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.

2 participants