Skip to content

Conversation

Michal-Martinek
Copy link

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

Description:

Please complete the following:

  • I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced

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

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

Copy link
Contributor

coderabbitai bot commented Sep 5, 2025

Walkthrough

Removed an explicit disable of @typescript-eslint/no-unused-expressions in ESLint config and replaced several short-circuit/no-op expressions with explicit conditional guards and instance checks across client modal handling, event click handlers, and a railroad redraw path; added console warnings for missing/mistyped modals.

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 configured and will follow inherited/default settings.
Client modal guards
src/client/Main.ts
Replaced bare short-circuit/no-op checks with explicit existence and instanceof guards for multiple modals; added console.warn when elements are missing or wrong type; gated startingModal.show() and guarded single-player modal access.
Event click guards
src/client/graphics/layers/EventsDisplay.ts
Replaced inline short-circuit emits with if-guards 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 related building units.

Sequence Diagram(s)

sequenceDiagram
  participant U as User
  participant Main as Main.ts
  participant Registry as Modal Registry
  participant Modal as Specific Modal

  U->>Main: request open modal
  Main->>Registry: lookup modal element
  alt element exists && instanceof expected
    Main->>Modal: show()/open()
  else missing or wrong type
    Main->>Main: console.warn("Modal missing or wrong type")
  end
Loading
sequenceDiagram
  participant Row as EventsDisplay Row
  participant Bus as EventBus

  Row->>Row: user clicks
  alt event.focusID present
    Row->>Bus: emit GoToPlayerEvent(focusID)
  end
  alt event.unitView present
    Row->>Bus: emit GoToUnitEvent(unitView)
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Assessment against linked issues

Objective Addressed Explanation
Enable the @typescript-eslint/no-unused-expressions rule — set rule active in config (#1790) The PR removed the explicit "off" entry but does not explicitly enable the rule; final enabled state depends on inherited config.
Replace short-circuit/type-assertion patterns or add inline ESLint disables (#1790)

Possibly related PRs

Suggested labels

DevOps - CICD

Suggested reviewers

  • evanpelle
  • scottanderson

Poem

Short-circuits slept and woke to ifs,
Modals checked with gentle whiffs.
ESLint freed from explicit off,
Warnings whisper, faults turn soft.
Clicks and rails now tidy, swift.

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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

content: this.getEventDescription(event),
onClick: () => {
event.focusID &&
if (event.focusID)
Copy link
Collaborator

Choose a reason for hiding this comment

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

is the related to the no-unused-expressions?

Copy link
Author

Choose a reason for hiding this comment

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

yes, otherwise linter complaints because result of this expression is not used.
1038:39 error Expected an assignment or function call and instead saw an expression @typescript-eslint/no-unused-expressions

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.

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