Skip to content

Conversation

@Michal-Martinek
Copy link
Contributor

@Michal-Martinek Michal-Martinek commented Sep 5, 2025

Description:

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:

michal7952

@Michal-Martinek Michal-Martinek requested a review from a team as a code owner September 5, 2025 21:24
@CLAassistant
Copy link

CLAassistant commented Sep 5, 2025

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 5, 2025

Walkthrough

Removed the explicit disable of @typescript-eslint/no-unused-expressions from ESLint config and replaced several short-circuit/no-op expressions with explicit existence and instanceof guards; added console warnings for missing/wrong modal elements and converted inline conditional emits/calls into explicit if checks.

Changes

Cohort / File(s) Summary of Changes
ESLint config
eslint.config.js
Deleted the "@typescript-eslint/no-unused-expressions": "off" rule entry so the rule is no longer explicitly disabled.
Client modal guards
src/client/Main.ts
Added explicit existence and instanceof guards for multiple modal elements; emit console.warn when elements are missing or unexpected type; gated startingModal.show() and protected single-player modal access.
Event click guards
src/client/graphics/layers/EventsDisplay.ts
Replaced inline short-circuit emits with explicit if checks before emitting GoToPlayerEvent and GoToUnitEvent.
Railroad redraw guards
src/core/execution/RailroadExecution.ts
Replaced short-circuit calls with explicit if checks before calling touch() on building units.
Structure icons guard
src/client/graphics/layers/StructureIconsLayer.ts
Replaced optional-chaining short-circuit clearing of ghost container filters with an explicit existence check.

Sequence Diagram(s)

sequenceDiagram
  participant U as User
  participant Main as Main.ts
  participant Registry as Modal Registry
  participant Modal as Specific Modal
  rect rgba(100,150,220,0.08)
  U->>Main: request open modal
  Main->>Registry: lookup modal element
  alt exists && instanceof expected
    Main->>Modal: show()/open()
  else missing or wrong type
    Main->>Main: console.warn("modal missing or wrong type")
  end
  end
Loading
sequenceDiagram
  participant Row as EventsDisplay Row
  participant Bus as EventBus
  Row->>Row: user clicks row
  alt event.focusID present
    Row->>Bus: emit GoToPlayerEvent(focusID)
  end
  alt event.unitView present
    Row->>Bus: emit GoToUnitEvent(unitView)
  end
Loading
sequenceDiagram
  participant Renderer as RailroadExecution
  participant From as fromBuilding
  participant To as toBuilding
  Renderer->>Renderer: prepare redrawBuildings()
  alt fromUnit active
    Renderer->>From: touch()
  end
  alt toUnit active
    Renderer->>To: touch()
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • evanpelle
  • scottanderson

Poem

Short-circuits traded for careful ifs,
Modals checked before their shifts.
ESLint freed to raise a flag,
Warnings whisper, guards unlag.
Clicks and rails now tidy riffs.

Pre-merge checks

✅ Passed checks (5 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly states the main change of the pull request by describing the enablement of the specific ESLint rule @typescript-eslint/no-unused-expressions, which matches the removal of the disable line and the refactoring in the code. It directly reflects the core objective without extra noise or unrelated details. It is concise, specific, and meaningful for team members scanning the history.
Linked Issues Check ✅ Passed The code updates remove the explicit disable of the rule and replace all unused short-circuit and standalone expressions with explicit guards and warnings, satisfying the requirement to eliminate unused expressions under @typescript-eslint/no-unused-expressions. The changes align with issue #1790 goals and no cases remain that need inline ESLint disables.
Out of Scope Changes Check ✅ Passed All modifications focus on enabling the ESLint rule and refactoring patterns that violate it. There are no unrelated feature additions, styling changes, or unrelated refactors outside the scope of issue #1790.
Description Check ✅ Passed The pull request description clearly refers to fixing issue #1790 and outlines the necessary refactors from short-circuit expressions to proper if statements and the handling of standalone instanceof expressions. It matches the changes in the codebase and is directly related to the linked issue without being off-topic.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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

🧹 Nitpick comments (3)
src/client/graphics/layers/EventsDisplay.ts (1)

1038-1051: Drop redundant inner checks in onClick closures

You already branch on event.focusID / event.unitView above. The inner if adds noise without safety.

-                                    onClick: () => {
-                                      if (event.focusID)
-                                        this.emitGoToPlayerEvent(event.focusID);
-                                    },
+                                    onClick: () =>
+                                      this.emitGoToPlayerEvent(event.focusID!),
...
-                                      onClick: () => {
-                                        if (event.unitView)
-                                          this.emitGoToUnitEvent(
-                                            event.unitView,
-                                          );
-                                      },
+                                      onClick: () =>
+                                        this.emitGoToUnitEvent(event.unitView!),

If you prefer to avoid non-null assertions, capture into a const before the template map.

src/client/Main.ts (2)

108-111: Avoid unsafe cast; rely on runtime narrowing

Casting to NewsModal and then checking instanceof is redundant and can hide null at compile time.

-    const newsModal = document.querySelector("news-modal") as NewsModal;
-    if (!newsModal || !(newsModal instanceof NewsModal)) {
+    const newsModal = document.querySelector("news-modal");
+    if (!(newsModal instanceof NewsModal)) {
       console.warn("News modal element not found");
     }

96-430: Reduce repetition: introduce a tiny helper to fetch-and-narrow custom elements

You repeat “query + instanceof + warn” many times. A small helper improves readability and safety.

function getEl<T extends Element>(
  selector: string,
  Ctor: abstract new (...args: any[]) => T,
  name: string,
): T | null {
  const el = document.querySelector(selector);
  if (el instanceof Ctor) return el;
  console.warn(`${name} element not found`);
  return null;
}

// Usage
const spModal = getEl("single-player-modal", SinglePlayerModal, "Singleplayer modal");
const hlpModal = getEl("help-modal", HelpModal, "Help modal");

This keeps code clean and idiomatic TS without extra libs.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0c9149e and 9340f8f.

📒 Files selected for processing (4)
  • eslint.config.js (1 hunks)
  • src/client/Main.ts (10 hunks)
  • src/client/graphics/layers/EventsDisplay.ts (2 hunks)
  • src/core/execution/RailroadExecution.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-09T02:20:43.637Z
Learnt from: VariableVince
PR: openfrontio/OpenFrontIO#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/Main.ts
🔇 Additional comments (2)
src/core/execution/RailroadExecution.ts (1)

167-169: Guard unit access to avoid runtime errors
Replace direct calls with local variables and optional chaining:

-    if (this.railRoad.from.unit.isActive()) this.railRoad.from.unit.touch();
-    if (this.railRoad.to.unit.isActive()) this.railRoad.to.unit.touch();
+    const fromUnit = this.railRoad.from.unit;
+    if (fromUnit?.isActive()) fromUnit.touch();
+    const toUnit = this.railRoad.to.unit;
+    if (toUnit?.isActive()) toUnit.touch();

If unit is always non-null here by contract, you can omit the ?. checks and treat this as a nit. Otherwise, keep the guards.

src/client/Main.ts (1)

575-577: Nice: explicit guard before showing starting modal

Clear and robust. Matches the lint rule’s intent.

coderabbitai[bot]
coderabbitai bot previously approved these changes Sep 7, 2025
coderabbitai[bot]
coderabbitai bot previously approved these changes Sep 7, 2025
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: 0

♻️ Duplicate comments (8)
src/client/Main.ts (8)

166-171: Still unsafe: spModal.open() can run when element is missing

You warn but the click handler calls open() unguarded. This can crash at runtime.

Minimal, typesafe fix:

-    const spModal = document.querySelector(
-      "single-player-modal",
-    ) as SinglePlayerModal;
+    const spModal = document.querySelector(
+      "single-player-modal",
+    ) as SinglePlayerModal | null;
@@
-      if (this.usernameInput?.isValid()) {
-        spModal.open();
-      }
+      if (this.usernameInput?.isValid()) {
+        spModal?.open();
+      }

Also applies to: 175-179


187-190: Same pattern: hlpModal.open() unguarded

The click handler can call open() on a missing element.

-    const hlpModal = document.querySelector("help-modal") as HelpModal;
+    const hlpModal = document.querySelector("help-modal") as HelpModal | null;
@@
-    helpButton.addEventListener("click", () => {
-      hlpModal.open();
-    });
+    helpButton.addEventListener("click", () => hlpModal?.open());

Also applies to: 193-195


197-203: Same pattern: flagInputModal.open() unguarded

Can throw when the element is absent.

-    const flagInputModal = document.querySelector(
-      "flag-input-modal",
-    ) as FlagInputModal;
+    const flagInputModal = document.querySelector(
+      "flag-input-modal",
+    ) as FlagInputModal | null;
@@
-    flgInput.addEventListener("click", () => {
-      flagInputModal.open();
-    });
+    flgInput.addEventListener("click", () => flagInputModal?.open());

Also applies to: 206-208


210-218: Immediate usages of patternsModal can crash; return early after warn

You set properties and call methods right after a warn, which will throw if the element is missing or wrong type. Early-return keeps later code safe.

-    this.patternsModal = document.querySelector(
+    this.patternsModal = document.querySelector(
       "territory-patterns-modal",
     ) as TerritoryPatternsModal;
     if (
       !this.patternsModal ||
       !(this.patternsModal instanceof TerritoryPatternsModal)
     ) {
       console.warn("Territory patterns modal element not found");
+      return;
     }

Also applies to: 224-228


230-238: tokenLoginModal is used later without checks

this.tokenLoginModal.open(token) in hash handlers will throw if the element is missing.

Two small, local fixes:

-    this.tokenLoginModal = document.querySelector(
-      "token-login",
-    ) as TokenLoginModal;
+    this.tokenLoginModal = document.querySelector(
+      "token-login",
+    ) as TokenLoginModal | null;
-        this.tokenLoginModal.open(token);
+        if (this.tokenLoginModal instanceof TokenLoginModal) {
+          this.tokenLoginModal.open(token);
+        } else {
+          console.warn("Token login modal element not found");
+        }
-      this.tokenLoginModal.open(token);
+      if (this.tokenLoginModal instanceof TokenLoginModal) {
+        this.tokenLoginModal.open(token);
+      } else {
+        console.warn("Token login modal element not found");
+      }

Also applies to: 478-479, 496-498


346-351: settingsModal.open() should be guarded at bind time

Attach the listener only when the element is valid.

-    const settingsModal = document.querySelector(
-      "user-setting",
-    ) as UserSettingModal;
-    if (!settingsModal || !(settingsModal instanceof UserSettingModal)) {
-      console.warn("User settings modal element not found");
-    }
-    document
-      .getElementById("settings-button")
-      ?.addEventListener("click", () => {
-        settingsModal.open();
-      });
+    const settingsEl = document.querySelector("user-setting");
+    if (settingsEl instanceof UserSettingModal) {
+      document.getElementById("settings-button")?.addEventListener("click", () => {
+        settingsEl.open();
+      });
+    } else {
+      console.warn("User settings modal element not found");
+    }

Also applies to: 352-356


358-363: hostModal.open() should be guarded

Same issue: open() called even if element is missing.

-    const hostModal = document.querySelector(
-      "host-lobby-modal",
-    ) as HostPrivateLobbyModal;
-    if (!hostModal || !(hostModal instanceof HostPrivateLobbyModal)) {
-      console.warn("Host private lobby modal element not found");
-    }
+    const hostModal = document.querySelector("host-lobby-modal");
+    if (!(hostModal instanceof HostPrivateLobbyModal)) {
+      console.warn("Host private lobby modal element not found");
+    }
@@
-        hostModal.open();
+        if (hostModal instanceof HostPrivateLobbyModal) hostModal.open();

Also applies to: 366-371


373-378: joinModal is assumed present later; enforce invariant or guard uses

Downstream code calls open()/close() without checks. Fail fast to keep the property non-null, or make it nullable and guard every use (more churn). Recommend fail-fast.

-    this.joinModal = document.querySelector(
+    this.joinModal = document.querySelector(
       "join-private-lobby-modal",
     ) as JoinPrivateLobbyModal;
-    if (!this.joinModal || !(this.joinModal instanceof JoinPrivateLobbyModal)) {
-      console.warn("Join private lobby modal element not found");
-    }
+    if (!(this.joinModal instanceof JoinPrivateLobbyModal)) {
+      throw new Error("JoinPrivateLobbyModal element not found");
+    }

Also applies to: 385-388

🧹 Nitpick comments (2)
src/client/Main.ts (2)

107-111: Simplify the guard and avoid unsafe as cast

newsModal || is redundant; null instanceof NewsModal is already false. Also prefer runtime-safe narrowing without as.

-    const newsModal = document.querySelector("news-modal") as NewsModal;
-    if (!newsModal || !(newsModal instanceof NewsModal)) {
+    const newsEl = document.querySelector("news-modal");
+    if (!(newsEl instanceof NewsModal)) {
       console.warn("News modal element not found");
     }

575-577: LGTM: safe show for GameStartingModal

Good guard; avoids calling show() on a wrong/missing element. Minor nit: instanceof already handles null, so the leading truthy check is redundant.

-        if (startingModal && startingModal instanceof GameStartingModal) {
+        if (startingModal instanceof GameStartingModal) {
           startingModal.show();
         }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e701dc9 and 36bd029.

📒 Files selected for processing (1)
  • src/client/Main.ts (10 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: Michal-Martinek
PR: openfrontio/OpenFrontIO#2014
File: eslint.config.js:43-43
Timestamp: 2025-09-06T07:10:42.249Z
Learning: When analyzing potential no-unused-expressions violations, always examine the full context. Expressions used in template literals, filter predicates, assignments, or other consuming contexts are not violations even if they use short-circuit operators like &&.
Learnt from: Michal-Martinek
PR: openfrontio/OpenFrontIO#2014
File: eslint.config.js:43-43
Timestamp: 2025-09-06T07:10:42.248Z
Learning: The `typescript-eslint/no-unused-expressions` ESLint rule only flags standalone expression statements that don't consume their results or have side effects. Expressions that are part of return statements, assignments, or other consuming contexts are not violations of this rule.
📚 Learning: 2025-06-09T02:20:43.637Z
Learnt from: VariableVince
PR: openfrontio/OpenFrontIO#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/Main.ts

coderabbitai[bot]
coderabbitai bot previously approved these changes Sep 7, 2025
@Michal-Martinek
Copy link
Contributor Author

@evanpelle @scottanderson review please, i have this opened for some weeks now.
And i think I fixed all the comments, so please let this move further. Thx

@evanpelle
Copy link
Collaborator

looks like there's a merge conflict

coderabbitai[bot]
coderabbitai bot previously approved these changes Oct 11, 2025
@Michal-Martinek
Copy link
Contributor Author

@evanpelle sorry for that, just rebased to resolve the conflict.
I wonder if it's a problem if not all commits pass tests, only the last one?

@TheGiraffe3 TheGiraffe3 changed the base branch from main to v26 October 13, 2025 04:37
@TheGiraffe3 TheGiraffe3 dismissed coderabbitai[bot]’s stale review October 13, 2025 04:37

The base branch was changed.

@TheGiraffe3
Copy link
Member

As long as the current version of the PR passes, there won't be problems.

@TheGiraffe3 TheGiraffe3 added this to the v26 milestone Oct 13, 2025
@TheGiraffe3 TheGiraffe3 added the DevOps - CICD Continuous Integration and Continuous Delivery tools, including GitHub Actions label Oct 13, 2025
Currently raising `console.warn` on fauilure
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: 0

🧹 Nitpick comments (1)
src/client/graphics/layers/StructureIconsLayer.ts (1)

258-260: Conversion looks good.

The short-circuit expression has been correctly converted to an explicit if statement, making the code more readable and meeting the PR objectives.

Optional simplification:

Since container is typed as PIXI.Container (non-nullable) in the ghostUnit type definition (lines 58-62), the check could be simplified:

-        if (this.ghostUnit?.container) {
+        if (this.ghostUnit) {
           this.ghostUnit.container.filters = [];
         }

The current check works fine but is slightly redundant—container is always present when ghostUnit is not null.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 66f2eb4 and 086a58f.

📒 Files selected for processing (5)
  • eslint.config.js (0 hunks)
  • src/client/Main.ts (10 hunks)
  • src/client/graphics/layers/EventsDisplay.ts (2 hunks)
  • src/client/graphics/layers/StructureIconsLayer.ts (1 hunks)
  • src/core/execution/RailroadExecution.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • eslint.config.js
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/client/graphics/layers/EventsDisplay.ts
  • src/core/execution/RailroadExecution.ts
  • src/client/Main.ts

@evanpelle evanpelle merged commit 5224136 into openfrontio:v26 Oct 14, 2025
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

DevOps - CICD Continuous Integration and Continuous Delivery tools, including GitHub Actions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enable the @typescript-eslint/no-unused-expressions eslint rule

4 participants