Skip to content

Conversation

@ShiralynDev
Copy link
Contributor

@ShiralynDev ShiralynDev commented Jan 13, 2026

This makes the map not crash on xbox servers and supports their flags in the menu. Xbox users are shown as "Unknown [XBOX]" More can be done with the simrail user endpoint it seems, not tried it yet https://panel.simrail.eu:8084/users-open/[ID]. This does require the PR on simrail-types to be merged.

Summary by CodeRabbit

  • New Features

    • Added Xbox player support with platform-aware user handling and Xbox-specific display placeholders
    • Enhanced server flag/region resolution to recognize Xbox server codes and map them to appropriate flags
  • Chores

    • Updated a package dependency version
    • Added new Xbox server configuration entries to the server list

✏️ Tip: You can customize this high-level summary in your review settings.

Copy link
Contributor Author

@ShiralynDev ShiralynDev left a comment

Choose a reason for hiding this comment

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

I don't think I ended up using userID and platform since the data types use the ControlledBy[XBOX/Steam]ID and therefor I think I could remove it but I will have to look at it again just to make sure.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds basic Xbox platform support to prevent crashes on Xbox servers and displays Xbox server flags in the menu. Xbox users are shown as "Unknown [XBOX]" in the UI until further integration is completed.

Changes:

  • Updated @simrail/types dependency to version 0.0.5 for Xbox type support
  • Added Xbox server configurations (XBX1, XBX2, XBX3) with flag mappings
  • Modified user handling logic to support both Steam and Xbox IDs across components

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
package.json Updated @simrail/types dependency to support Xbox types
packages/map/components/serverTimes.json Added server time configurations for three Xbox servers
packages/map/pages/index.tsx Added logic to handle Xbox server codes and map them to appropriate flag icons
packages/map/components/SpotlightSearch.tsx Extended user class and search functionality to handle both Steam and Xbox user IDs
packages/map/components/SelectedTrainPopup.tsx Added TODO comment for Xbox player support
packages/map/components/Markers/StationMarker.tsx Added Xbox user handling for dispatchers with string comparison checks

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}
if (stations[i].DispatchedBy[0] && stations[i].DispatchedBy[0].XboxId != null) {
userIDs.push(stations[i].DispatchedBy[0].XboxId);
platforms.push("xbox")
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

Missing semicolon at the end of the statement. This is inconsistent with the code style used elsewhere in the file (e.g., line 68).

Copilot uses AI. Check for mistakes.
}

if (train.TrainData.ControlledByXboxID) {
username = "Unknown [XBOX]" // make function for getting xbox usernames
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

Missing semicolon at the end of the statement. This is inconsistent with the code style used elsewhere in the file.

Copilot uses AI. Check for mistakes.
}

if (station.DispatchedBy[0] && station.DispatchedBy[0].XboxId) {
username = "Unknown [XBOX]" // make function for getting xbox usernames https://panel.simrail.eu:8084/users-open/
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

Missing semicolon at the end of the statement. This is inconsistent with the code style used elsewhere in the file.

Copilot uses AI. Check for mistakes.
Comment on lines +95 to +104
(() => {
const letters =
server.ServerCode.match(/[A-Za-z]+/)?.at(0) ??
server.ServerCode.slice(0, 2);
if (letters.toUpperCase() === "XBX") {
const numMatch = server.ServerCode.match(/^XBX(\d+)/i);
return numMatch ? `XBX${numMatch[1]}` : "XBX";
}
return letters;
})()
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

This IIFE logic for handling Xbox server codes adds complexity inline. Consider extracting this into a separate function like getServerFlagCode(serverCode: string) to improve readability and testability.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

it would add complexity by having to make sure the serverID always sends the number with it

Comment on lines +182 to 185
if (lang === "XBX1") lang = "PL";
if (lang === "XBX2") lang = "INT";
if (lang === "XBX3") lang = "INT";
if (lang === "EN") lang = "GB";
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

These individual if statements for mapping Xbox codes could be simplified using a switch statement or an object lookup for better maintainability as more Xbox servers are added.

Suggested change
if (lang === "XBX1") lang = "PL";
if (lang === "XBX2") lang = "INT";
if (lang === "XBX3") lang = "INT";
if (lang === "EN") lang = "GB";
const langOverrides: Record<string, string> = {
XBX1: "PL",
XBX2: "INT",
XBX3: "INT",
EN: "GB",
};
if (langOverrides[lang]) {
lang = langOverrides[lang];
}

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Im following the style of the current code

@simrail simrail deleted a comment from coderabbitai bot Jan 13, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 13, 2026

📝 Walkthrough

Walkthrough

Added Xbox platform support across UI lookup and rendering: dependency bump, platform-aware user/profile lookup (Steam vs Xbox), Xbox server entries, and flag/locale mapping for Xbox servers.

Changes

Cohort / File(s) Summary
Dependency
package.json
Bumped @simrail/types from ^0.0.4 to ^0.0.5.
Xbox Server Configuration
packages/map/components/serverTimes.json
Appended three Xbox server entries: xbx1, xbx2, xbx3 (offsetSeconds: 0).
Platform-aware user & lookup
packages/map/components/SpotlightSearch.tsx
Replaced steamID-centric user model with ID+platform; updated user class constructor and getUsernames signature to accept parallel platforms array; callers now pass IDs with platform tags and logic branches by platform when gathering usernames/actions.
Profile fetching and rendering
packages/map/components/Markers/StationMarker.tsx, packages/map/components/SelectedTrainPopup.tsx
StationMarker: conditional Steam profile fetch skipped when SteamId is "null"; if XboxId present, avatar forced null and username set to Unknown [XBOX]. SelectedTrainPopup: added inline comment referencing Xbox support in the Promise chain.
Flag / locale mapping
packages/map/pages/index.tsx
New server code to flag mapping: derive letter codes or first two chars, capture digits for XBX codes (renders XBX+digits), and remap XBX1→PL, XBX2→INT, XBX3→INT before existing language/flag logic.

Sequence Diagram(s)

mermaid
sequenceDiagram
actor User
participant SpotlightSearch
participant UsernameCache
participant SteamProfileService
participant MapComponents
User ->> SpotlightSearch: open/search (collect IDs + platforms)
SpotlightSearch ->> UsernameCache: lookup IDs
alt cache miss & platform == Steam
SpotlightSearch ->> SteamProfileService: fetch profile by SteamID
SteamProfileService -->> SpotlightSearch: profile (avatar, username)
else platform == Xbox
SpotlightSearch -->> SpotlightSearch: skip Steam fetch, use Xbox placeholder
end
SpotlightSearch ->> MapComponents: update usernames/actions
MapComponents ->> MapComponents: render StationMarker / SelectedTrainPopup

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Xbox hops into the game with a cheer,
Steam faces kept, and new console names appear,
Flags now map XBX with digits in tow,
Profiles fetched where Steam IDs glow,
A floppy-eared patch — hop, review, and go!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Basic xbox support' accurately captures the main objective: adding Xbox support to prevent crashes and display Xbox flags and users.

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

✨ Finishing touches
  • 📝 Generate docstrings

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

@coderabbitai
Copy link

coderabbitai bot commented Jan 13, 2026

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":404,"request":{"method":"PATCH","url":"https://api.github.com/repos/simrail/map-v2/issues/comments/3745153503","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","authorization":"token [REDACTED]","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- walkthrough_start -->\n\n<details>\n<summary>📝 Walkthrough</summary>\n\n## Walkthrough\n\nDependencies updated to support Xbox functionality. Station markers now conditionally fetch Steam data based on player platform. Spotlight search refactored to track user IDs and platforms separately. Xbox server entries added to configuration. UI labels and flag icons updated with Xbox-specific mappings.\n\n## Changes\n\n| Cohort / File(s) | Summary |\n|---|---|\n| **Dependency & Configuration Updates** <br> `package.json`, `packages/map/components/serverTimes.json` | Updated \"@simrail/types\" to ^0.0.5. Added three Xbox server entries (xbx1, xbx2, xbx3) with 0 offset seconds. |\n| **Platform Detection & User Management** <br> `packages/map/components/StationMarker.tsx`, `packages/map/components/SpotlightSearch.tsx` | Station marker now skips Steam fetch when SteamId is \"null\" and detects Xbox players via XboxId. Spotlight search refactored to track platform alongside user IDs, conditionally querying Steam only for \"steam\" platform users and creating \"Unknown [XBOX]\" placeholders for Xbox users. |\n| **UI & Display Updates** <br> `packages/map/components/SelectedTrainPopup.tsx`, `packages/map/pages/index.tsx` | Added comment noting Xbox support in useEffect. Server flag labels now extract numeric suffixes for XBX-prefixed codes. FlagIcon component now maps XBX1 → PL, XBX2 → INT, XBX3 → INT before standard flag resolution. |\n\n## Sequence Diagram(s)\n\n```mermaid\nsequenceDiagram\n    actor User\n    participant SpotlightSearch\n    participant TrainData/DispatchedBy\n    participant PlatformDetector\n    participant getUsernames\n    participant SteamAPI\n    participant UI\n\n    User->>SpotlightSearch: Trigger spotlight search\n    SpotlightSearch->>TrainData/DispatchedBy: Read train and station data\n    TrainData/DispatchedBy-->>SpotlightSearch: Return SteamId, XboxId\n    \n    SpotlightSearch->>PlatformDetector: Detect platform from SteamId/XboxId\n    PlatformDetector-->>SpotlightSearch: Return platform (\"steam\" or \"xbox\")\n    \n    SpotlightSearch->>SpotlightSearch: Build userIDs and platforms arrays\n    SpotlightSearch->>getUsernames: Call with userIDs and platforms arrays\n    \n    alt Platform is \"steam\"\n        getUsernames->>SteamAPI: Fetch avatar and username\n        SteamAPI-->>getUsernames: Return Steam data\n    else Platform is \"xbox\"\n        getUsernames-->>getUsernames: Set username to \"Unknown [XBOX]\"\n    end\n    \n    getUsernames-->>SpotlightSearch: Return resolved usernames\n    SpotlightSearch->>UI: Display trains/stations with platform-aware names\n    UI-->>User: Render spotlight results\n```\n\n## Estimated code review effort\n\n🎯 3 (Moderate) | ⏱️ ~25 minutes\n\n## Poem\n\n> 🐰 Xbox hops onto the map today,  \n> With platform-aware lookups leading the way,  \n> Steam and console players now play as one,  \n> Flag icons mapped and searches all done! 🎮✨\n\n</details>\n\n<!-- walkthrough_end -->\n\n\n<!-- pre_merge_checks_walkthrough_start -->\n\n<details>\n<summary>🚥 Pre-merge checks | ✅ 2 | ❌ 1</summary>\n\n<details>\n<summary>❌ Failed checks (1 warning)</summary>\n\n|     Check name     | Status     | Explanation                                                                          | Resolution                                                                         |\n| :----------------: | :--------- | :----------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------- |\n| Docstring Coverage | ⚠️ Warning | Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. |\n\n</details>\n<details>\n<summary>✅ Passed checks (2 passed)</summary>\n\n|     Check name    | Status   | Explanation                                                                                                                                                                       |\n| :---------------: | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled.                                                                                                                       |\n|    Title check    | ✅ Passed | The title \"Basic xbox support\" accurately reflects the main objective of the pull request, which adds foundational Xbox support to prevent crashes and handle Xbox-related flags. |\n\n</details>\n\n<sub>✏️ Tip: You can configure your own custom pre-merge checks in the settings.</sub>\n\n</details>\n\n<!-- pre_merge_checks_walkthrough_end -->\n\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing touches</summary>\n\n- [ ] <!-- {\"checkboxId\": \"7962f53c-55bc-4827-bfbf-6a18da830691\"} --> 📝 Generate docstrings\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n\n<!-- tips_start -->\n\n---\n\n\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->\n\n<!-- internal state start -->\n\n\n<!-- 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 -->\n\n<!-- internal state end -->"},"request":{"retryCount":1}},"response":{"url":"https://api.github.com/repos/simrail/map-v2/issues/comments/3745153503","status":404,"headers":{"access-control-allow-origin":"*","access-control-expose-headers":"ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset","content-encoding":"gzip","content-security-policy":"default-src 'none'","content-type":"application/json; charset=utf-8","date":"Tue, 13 Jan 2026 16:09:48 GMT","referrer-policy":"origin-when-cross-origin, strict-origin-when-cross-origin","server":"github.com","strict-transport-security":"max-age=31536000; includeSubdomains; preload","transfer-encoding":"chunked","vary":"Accept-Encoding, Accept, X-Requested-With","x-accepted-github-permissions":"issues=write; pull_requests=write","x-content-type-options":"nosniff","x-frame-options":"deny","x-github-api-version-selected":"2022-11-28","x-github-media-type":"github.v3; format=json","x-github-request-id":"B079:C5D9C:748D6B:1FF96E8:69666E4C","x-ratelimit-limit":"5000","x-ratelimit-remaining":"4813","x-ratelimit-reset":"1768323853","x-ratelimit-resource":"core","x-ratelimit-used":"187","x-xss-protection":"0"},"data":{"message":"Not Found","documentation_url":"https://docs.github.com/rest/issues/comments#update-an-issue-comment","status":"404"}}}

Copy link

@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: 6

Caution

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

⚠️ Outside diff range comments (1)
packages/map/components/SelectedTrainPopup.tsx (1)

29-35: Xbox players are not actually handled here.

The comment states "support xbox players" but this code only calls getSteamProfileOrBot, which returns "BOT" when ControlledBySteamID is null/undefined. For Xbox players, this will incorrectly show "BOT" instead of "Unknown [XBOX]" as displayed in StationMarker.tsx and SpotlightSearch.tsx.

To maintain consistency across the codebase, consider adding Xbox ID handling similar to the other components.

Suggested fix
 	useEffect(() => {
-		if (selectedTrain)
-			getSteamProfileOrBot(selectedTrain.TrainData.ControlledBySteamID).then( // support xbox players
-				// @ts-ignore
-				setData,
-			);
+		if (selectedTrain) {
+			if (selectedTrain.TrainData.ControlledByXboxID) {
+				setData([null, "Unknown [XBOX]"]);
+			} else {
+				getSteamProfileOrBot(selectedTrain.TrainData.ControlledBySteamID).then(
+					// @ts-ignore
+					setData,
+				);
+			}
+		}
 	}, [selectedTrain]);
🤖 Fix all issues with AI agents
In @packages/map/components/Markers/StationMarker.tsx:
- Around line 24-31: The conditional in StationMarker.tsx uses non-strict
inequality (station.DispatchedBy[0].SteamId != "null"); replace it with strict
inequality (!==) to avoid type-coercion issues — update the if check around the
fetch/profile logic (the block that calls
fetch(...steam/${station.DispatchedBy[0].SteamId}), awaits avatarRequest.json(),
and calls setAvatar/setUsername) to use !== "null".
- Around line 32-35: The code uses a non-strict inequality and may
unintentionally override Steam data: replace the `!=` check on
station.DispatchedBy[0].XboxId with strict `!==`, and change the conditional
structure so Xbox handling does not unconditionally override Steam handling (use
`else if` or explicitly document precedence) modifying the logic around
setAvatar and setUsername in the StationMarker component to preserve mutual
exclusivity or clarify intent.

In @packages/map/components/SpotlightSearch.tsx:
- Around line 60-70: In the for-loop inside SpotlightSearch
(SpotlightSearch.tsx) that iterates over trains, a train can currently push both
ControlledBySteamID and ControlledByXboxID which creates duplicate/misaligned
entries in the parallel arrays userIDs and platforms; update the logic in that
loop (the block checking trains[i].Type === "user") to treat the two checks as
mutually exclusive (use an else if or equivalent) so only one ID/platform is
pushed per train, keeping the null checks (trains[i] != null and TrainData.* !=
null) intact to preserve alignment between userIDs and platforms.
- Around line 72-81: In SpotlightSearch.tsx inside the loop over stations, the
logic pushing IDs pushes both Steam and Xbox for the same station; change the
second conditional to an else if so only one ID is pushed per station (e.g.,
keep the existing check for stations[i].DispatchedBy[0] &&
stations[i].DispatchedBy[0].SteamId != null then push Steam, and use else if
(stations[i].DispatchedBy[0] && stations[i].DispatchedBy[0].XboxId != null) to
push Xbox), updating the userIDs and platforms arrays accordingly to enforce
mutual exclusivity.
- Around line 120-124: The code is accessing station.DispatchedBy[0].SteamId
without optional chaining; replace that block to safely handle missing
arrays/entries by first extracting steamId with optional chaining (e.g., const
steamId = station.DispatchedBy?.[0]?.SteamId) and then set username using
usernamesCache.current.get(steamId) ?? "Unknown", or if steamId is undefined
just assign "Unknown"; update the logic around the username variable in
SpotlightSearch (the station.DispatchedBy/SteamId access and
usernamesCache.current.get call) to use optional chaining and nullish coalescing
to satisfy the linter.
- Around line 126-128: Replace the manual null-checks with optional chaining:
update the conditional that currently checks station.DispatchedBy[0] and
station.DispatchedBy[0].XboxId to use station.DispatchedBy?.[0]?.XboxId, leaving
the username assignment to "Unknown [XBOX]" in the same block; reference the
station variable and the username assignment in SpotlightSearch.tsx when making
this change.
🧹 Nitpick comments (2)
packages/map/components/Markers/StationMarker.tsx (1)

25-30: Missing error handling for fetch.

If the Steam profile fetch fails (network error, non-2xx response, or invalid JSON), the promise rejection will be unhandled and may cause silent failures. Consider wrapping in try-catch.

Suggested fix
 			if (station.DispatchedBy[0].SteamId !== "null") {
-				const avatarRequest = await fetch(
-					`https://simrail-edr.emeraldnetwork.xyz/steam/${station.DispatchedBy[0].SteamId}`,
-				);
-				const profile: ProfileResponse = await avatarRequest.json();
-				setAvatar(profile.avatar);
-				setUsername(profile.personaname);
+				try {
+					const avatarRequest = await fetch(
+						`https://simrail-edr.emeraldnetwork.xyz/steam/${station.DispatchedBy[0].SteamId}`,
+					);
+					const profile: ProfileResponse = await avatarRequest.json();
+					setAvatar(profile.avatar);
+					setUsername(profile.personaname);
+				} catch (error) {
+					console.error("Failed to fetch Steam profile:", error);
+					setUsername("Unknown");
+				}
 			}
packages/map/pages/index.tsx (1)

181-184: Consider consolidating flag mappings into an object.

The sequential if statements work but could be more maintainable as an object lookup, especially if more Xbox servers are added in the future.

Optional refactor
 				let lang = code.toUpperCase();
-
-				if (lang === "XBX1") lang = "PL";
-				if (lang === "XBX2") lang = "INT";
-				if (lang === "XBX3") lang = "INT";
+				const flagOverrides: Record<string, string> = {
+					XBX1: "PL",
+					XBX2: "INT",
+					XBX3: "INT",
+					EN: "GB",
+				};
+				lang = flagOverrides[lang] ?? lang;
 				if (lang === "EN") lang = "GB";
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 41068d7 and 599268b.

📒 Files selected for processing (6)
  • package.json
  • packages/map/components/Markers/StationMarker.tsx
  • packages/map/components/SelectedTrainPopup.tsx
  • packages/map/components/SpotlightSearch.tsx
  • packages/map/components/serverTimes.json
  • packages/map/pages/index.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
packages/map/components/SelectedTrainPopup.tsx (1)
packages/map/components/steam.ts (1)
  • getSteamProfileOrBot (8-16)
🪛 GitHub Actions: Code quality
packages/map/components/Markers/StationMarker.tsx

[warning] 24-24: lint/suspicious/noDoubleEquals: Use !== instead of !=


[warning] 32-32: lint/suspicious/noDoubleEquals: Use !== instead of !=

packages/map/components/SpotlightSearch.tsx

[warning] 120-120: lint/complexity/useOptionalChain: Change to an optional chain.


[warning] 126-126: lint/complexity/useOptionalChain: Change to an optional chain.

🔇 Additional comments (5)
packages/map/components/serverTimes.json (1)

65-77: LGTM! New Xbox server entries added correctly.

The JSON structure is valid, and the naming convention (xbx1, xbx2, xbx3) is consistent with existing server codes. Using offsetSeconds: 0 aligns with the "basic support" intent of this PR.

If Xbox servers require timezone-specific offsets in the future (like fr1 with 3600 or others with negative offsets), these values can be updated accordingly.

package.json (1)

13-13: Dependency update for Xbox type support.

The version bump to @simrail/types ^0.0.5 is required for the new Xbox-related type definitions used throughout this PR.

Per the PR description, ensure the corresponding simrail-types PR is merged before merging this PR.

packages/map/pages/index.tsx (1)

95-104: XBX server code extraction logic looks correct.

The IIFE correctly handles the XBX prefix case by extracting the numeric suffix (e.g., "XBX1" from "XBX123") and falling back appropriately.

packages/map/components/SpotlightSearch.tsx (2)

97-99: Approved: Xbox placeholder handling for trains.

The "Unknown [XBOX]" placeholder is consistent with the approach in StationMarker.tsx. The TODO comment about future Xbox username lookup is helpful.


15-25: Remove the unused user class and usernames export.

The user class and the exported usernames array are never instantiated or populated anywhere in the codebase and should be removed. (Note: the file does use usernamesCache, which is a separate useRef<Map> that serves a different purpose.)

Comment on lines +24 to +31
if (station.DispatchedBy[0].SteamId != "null") {
const avatarRequest = await fetch(
`https://simrail-edr.emeraldnetwork.xyz/steam/${station.DispatchedBy[0].SteamId}`,
);
const profile: ProfileResponse = await avatarRequest.json();
setAvatar(profile.avatar);
setUsername(profile.personaname);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use strict inequality (!==) as flagged by linter.

The pipeline flagged this line for using != instead of !==. Using strict equality is a JavaScript best practice to avoid type coercion issues.

Suggested fix
-			if (station.DispatchedBy[0].SteamId != "null") {
+			if (station.DispatchedBy[0].SteamId !== "null") {
📝 Committable suggestion

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

Suggested change
if (station.DispatchedBy[0].SteamId != "null") {
const avatarRequest = await fetch(
`https://simrail-edr.emeraldnetwork.xyz/steam/${station.DispatchedBy[0].SteamId}`,
);
const profile: ProfileResponse = await avatarRequest.json();
setAvatar(profile.avatar);
setUsername(profile.personaname);
}
if (station.DispatchedBy[0].SteamId !== "null") {
const avatarRequest = await fetch(
`https://simrail-edr.emeraldnetwork.xyz/steam/${station.DispatchedBy[0].SteamId}`,
);
const profile: ProfileResponse = await avatarRequest.json();
setAvatar(profile.avatar);
setUsername(profile.personaname);
}
🧰 Tools
🪛 GitHub Actions: Code quality

[warning] 24-24: lint/suspicious/noDoubleEquals: Use !== instead of !=

🤖 Prompt for AI Agents
In @packages/map/components/Markers/StationMarker.tsx around lines 24 - 31, The
conditional in StationMarker.tsx uses non-strict inequality
(station.DispatchedBy[0].SteamId != "null"); replace it with strict inequality
(!==) to avoid type-coercion issues — update the if check around the
fetch/profile logic (the block that calls
fetch(...steam/${station.DispatchedBy[0].SteamId}), awaits avatarRequest.json(),
and calls setAvatar/setUsername) to use !== "null".

Comment on lines +32 to +35
if (station.DispatchedBy[0].XboxId != "null") {
setAvatar(null);
setUsername("Unknown [XBOX]");
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use strict inequality (!==) and consider order-of-precedence logic.

  1. The pipeline flagged this line for using != instead of !==.
  2. If both SteamId and XboxId are present, the Xbox block will override the Steam profile data. If this is intentional, a comment would clarify the precedence. If not, consider using else if.
Suggested fix with else-if for mutual exclusivity
-			if (station.DispatchedBy[0].SteamId != "null") {
+			if (station.DispatchedBy[0].SteamId !== "null") {
 				const avatarRequest = await fetch(
 					`https://simrail-edr.emeraldnetwork.xyz/steam/${station.DispatchedBy[0].SteamId}`,
 				);
 				const profile: ProfileResponse = await avatarRequest.json();
 				setAvatar(profile.avatar);
 				setUsername(profile.personaname);
-			}
-			if (station.DispatchedBy[0].XboxId != "null") {
+			} else if (station.DispatchedBy[0].XboxId !== "null") {
 				setAvatar(null);
 				setUsername("Unknown [XBOX]");
 			}
🧰 Tools
🪛 GitHub Actions: Code quality

[warning] 32-32: lint/suspicious/noDoubleEquals: Use !== instead of !=

🤖 Prompt for AI Agents
In @packages/map/components/Markers/StationMarker.tsx around lines 32 - 35, The
code uses a non-strict inequality and may unintentionally override Steam data:
replace the `!=` check on station.DispatchedBy[0].XboxId with strict `!==`, and
change the conditional structure so Xbox handling does not unconditionally
override Steam handling (use `else if` or explicitly document precedence)
modifying the logic around setAvatar and setUsername in the StationMarker
component to preserve mutual exclusivity or clarify intent.

Comment on lines +120 to 124
if (station.DispatchedBy[0] && station.DispatchedBy[0].SteamId) {
username =
usernamesCache.current.get(station.DispatchedBy[0].SteamId) ??
"Unknown";
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use optional chaining as flagged by linter.

The pipeline flagged this for lint/complexity/useOptionalChain.

Suggested fix
-				if (station.DispatchedBy[0] && station.DispatchedBy[0].SteamId) {
+				if (station.DispatchedBy[0]?.SteamId) {
🧰 Tools
🪛 GitHub Actions: Code quality

[warning] 120-120: lint/complexity/useOptionalChain: Change to an optional chain.

🤖 Prompt for AI Agents
In @packages/map/components/SpotlightSearch.tsx around lines 120 - 124, The code
is accessing station.DispatchedBy[0].SteamId without optional chaining; replace
that block to safely handle missing arrays/entries by first extracting steamId
with optional chaining (e.g., const steamId =
station.DispatchedBy?.[0]?.SteamId) and then set username using
usernamesCache.current.get(steamId) ?? "Unknown", or if steamId is undefined
just assign "Unknown"; update the logic around the username variable in
SpotlightSearch (the station.DispatchedBy/SteamId access and
usernamesCache.current.get call) to use optional chaining and nullish coalescing
to satisfy the linter.

Comment on lines +126 to +128
if (station.DispatchedBy[0] && station.DispatchedBy[0].XboxId) {
username = "Unknown [XBOX]" // make function for getting xbox usernames https://panel.simrail.eu:8084/users-open/
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use optional chaining as flagged by linter.

The pipeline flagged this for lint/complexity/useOptionalChain.

Suggested fix
-				if (station.DispatchedBy[0] && station.DispatchedBy[0].XboxId) {
+				if (station.DispatchedBy[0]?.XboxId) {
📝 Committable suggestion

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

Suggested change
if (station.DispatchedBy[0] && station.DispatchedBy[0].XboxId) {
username = "Unknown [XBOX]" // make function for getting xbox usernames https://panel.simrail.eu:8084/users-open/
}
if (station.DispatchedBy[0]?.XboxId) {
username = "Unknown [XBOX]" // make function for getting xbox usernames https://panel.simrail.eu:8084/users-open/
}
🧰 Tools
🪛 GitHub Actions: Code quality

[warning] 126-126: lint/complexity/useOptionalChain: Change to an optional chain.

🤖 Prompt for AI Agents
In @packages/map/components/SpotlightSearch.tsx around lines 126 - 128, Replace
the manual null-checks with optional chaining: update the conditional that
currently checks station.DispatchedBy[0] and station.DispatchedBy[0].XboxId to
use station.DispatchedBy?.[0]?.XboxId, leaving the username assignment to
"Unknown [XBOX]" in the same block; reference the station variable and the
username assignment in SpotlightSearch.tsx when making this change.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In @packages/map/components/Markers/StationMarker.tsx:
- Around line 24-35: In StationMarker.tsx the dispatch ID checks can both run
and overwrite each other and use loose inequality; change the second check to an
else if so Xbox logic is mutually exclusive with the Steam fetch, and replace !=
with !== for both comparisons (refer to station.DispatchedBy[0].SteamId and
station.DispatchedBy[0].XboxId and the avatar/username setAvatar/setUsername
calls) so Steam fetch result isn't immediately overwritten and strict equality
is used.

In @packages/map/components/SpotlightSearch.tsx:
- Line 83: The effect calls getUsernames(userIDs, platforms) but does not await
it, causing a race where spotlight actions are built before the username cache
is populated; modify the effect to await getUsernames(...) (or refactor to
separate effects) so usernames are resolved before constructing the actions in
buildSpotlightActions/buildActions (or whatever function builds the actions),
ensuring the async call to getUsernames is awaited and errors handled so the
actions use populated usernames instead of "Unknown".
- Around line 119-128: The two separate checks for SteamId and XboxId can both
run and let Xbox overwrite a valid Steam username; change the second check to an
else if to make them mutually exclusive and use optional chaining on
station.DispatchedBy (e.g., station.DispatchedBy?.[0]?.SteamId and
station.DispatchedBy?.[0]?.XboxId) when reading IDs and when calling
usernamesCache.current.get to avoid pipeline warnings and runtime errors.
- Around line 61-80: The checks are inconsistent: trains compare
ControlledBySteamID/ControlledByXboxID to the string "null" using !=, but
stations compare DispatchedBy[0].SteamId/XboxId to actual null; update station
checks to mirror trains by first ensuring DispatchedBy exists and has an element
(e.g., DispatchedBy && DispatchedBy.length > 0) and then compare SteamId/XboxId
to the string "null" using strict inequality (!== "null"); also replace any
remaining != with !== for ControlledBySteamID/ControlledByXboxID comparisons to
use strict equality consistently in the code handling trains and stations
(references: trains, trains[i].TrainData.ControlledBySteamID,
ControlledByXboxID, stations, stations[i].DispatchedBy, SteamId, XboxId).
🧹 Nitpick comments (2)
packages/map/pages/index.tsx (2)

95-104: Consider extracting the server code derivation to a helper function.

The inline IIFE works correctly but reduces readability. A named helper would clarify intent and make the logic easier to test.

Suggested refactor
+const getDisplayCode = (serverCode: string): string => {
+  const letters = serverCode.match(/[A-Za-z]+/)?.at(0) ?? serverCode.slice(0, 2);
+  if (letters.toUpperCase() === "XBX") {
+    const numMatch = serverCode.match(/^XBX(\d+)/i);
+    return numMatch ? `XBX${numMatch[1]}` : "XBX";
+  }
+  return letters;
+};

Then use it in the JSX:

<FlagIcon code={getDisplayCode(server.ServerCode)} />

181-184: Add a fallback for unknown XBX server codes.

If a new Xbox server (e.g., XBX4) is added to the API, it will pass through without remapping, causing flagPack["XBX4Flag"] lookup to fail and log an error with no flag displayed.

Consider adding a default fallback for any unrecognized XBX* code:

Suggested approach using a mapping object
-				let lang = code.toUpperCase();
-
-				if (lang === "XBX1") lang = "PL";
-				if (lang === "XBX2") lang = "INT";
-				if (lang === "XBX3") lang = "INT";
-				if (lang === "EN") lang = "GB";
+				const langMappings: Record<string, string> = {
+					XBX1: "PL",
+					XBX2: "INT",
+					XBX3: "INT",
+					EN: "GB",
+				};
+
+				let lang = code.toUpperCase();
+				if (lang in langMappings) {
+					lang = langMappings[lang];
+				} else if (lang.startsWith("XBX")) {
+					lang = "INT"; // Default fallback for unknown Xbox servers
+				}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 41068d7 and dbe8d69.

📒 Files selected for processing (6)
  • package.json
  • packages/map/components/Markers/StationMarker.tsx
  • packages/map/components/SelectedTrainPopup.tsx
  • packages/map/components/SpotlightSearch.tsx
  • packages/map/components/serverTimes.json
  • packages/map/pages/index.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
packages/map/components/SelectedTrainPopup.tsx (1)
packages/map/components/steam.ts (1)
  • getSteamProfileOrBot (8-16)
packages/map/components/SpotlightSearch.tsx (1)
packages/map/components/steam.ts (1)
  • getSteamProfileOrBot (8-16)
🪛 GitHub Actions: Code quality
packages/map/components/Markers/StationMarker.tsx

[warning] 24-24: lint/suspicious/noDoubleEquals: Use !== instead of !=


[warning] 32-32: lint/suspicious/noDoubleEquals: Use !== instead of !=

packages/map/components/SpotlightSearch.tsx

[warning] 62-62: lint/suspicious/noDoubleEquals: Use !== instead of !=


[warning] 66-66: lint/suspicious/noDoubleEquals: Use !== instead of !=


[warning] 119-119: lint/complexity/useOptionalChain: Change to an optional chain.


[warning] 125-125: lint/complexity/useOptionalChain: Change to an optional chain.

🔇 Additional comments (2)
package.json (1)

13-13: LGTM!

The dependency bump to @simrail/types ^0.0.5 is necessary for the new Xbox-related type definitions used throughout this PR.

packages/map/components/serverTimes.json (1)

66-77: LGTM!

The new Xbox server entries (xbx1, xbx2, xbx3) follow the existing structure and provide the necessary time offset configuration for Xbox servers.

Comment on lines +24 to +35
if (station.DispatchedBy[0].SteamId != "null") {
const avatarRequest = await fetch(
`https://simrail-edr.emeraldnetwork.xyz/steam/${station.DispatchedBy[0].SteamId}`,
);
const profile: ProfileResponse = await avatarRequest.json();
setAvatar(profile.avatar);
setUsername(profile.personaname);
}
if (station.DispatchedBy[0].XboxId != "null") {
setAvatar(null);
setUsername("Unknown [XBOX]");
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Logic issue: Both conditions can execute, causing unintended overwrites.

If both SteamId and XboxId are set (and neither equals "null"), the Steam profile will be fetched first, then immediately overwritten by the Xbox placeholder. Use else if for the Xbox condition to ensure mutual exclusivity.

Additionally, the pipeline flagged the use of != instead of !== for strict equality.

Proposed fix
 		if (station.DispatchedBy[0]) {
-			if (station.DispatchedBy[0].SteamId != "null") {
+			if (station.DispatchedBy[0].SteamId !== "null") {
 				const avatarRequest = await fetch(
 					`https://simrail-edr.emeraldnetwork.xyz/steam/${station.DispatchedBy[0].SteamId}`,
 				);
 				const profile: ProfileResponse = await avatarRequest.json();
 				setAvatar(profile.avatar);
 				setUsername(profile.personaname);
-			}
-			if (station.DispatchedBy[0].XboxId != "null") {
+			} else if (station.DispatchedBy[0].XboxId !== "null") {
 				setAvatar(null);
 				setUsername("Unknown [XBOX]");
 			}
🧰 Tools
🪛 GitHub Actions: Code quality

[warning] 24-24: lint/suspicious/noDoubleEquals: Use !== instead of !=


[warning] 32-32: lint/suspicious/noDoubleEquals: Use !== instead of !=

🤖 Prompt for AI Agents
In @packages/map/components/Markers/StationMarker.tsx around lines 24 - 35, In
StationMarker.tsx the dispatch ID checks can both run and overwrite each other
and use loose inequality; change the second check to an else if so Xbox logic is
mutually exclusive with the Steam fetch, and replace != with !== for both
comparisons (refer to station.DispatchedBy[0].SteamId and
station.DispatchedBy[0].XboxId and the avatar/username setAvatar/setUsername
calls) so Steam fetch result isn't immediately overwritten and strict equality
is used.

Comment on lines 30 to 34
if (selectedTrain)
getSteamProfileOrBot(selectedTrain.TrainData.ControlledBySteamID).then(
getSteamProfileOrBot(selectedTrain.TrainData.ControlledBySteamID).then( // support xbox players
// @ts-ignore
setData,
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Xbox support is incomplete in this component.

The comment suggests Xbox support, but the implementation only fetches Steam profiles. When ControlledBySteamID is null/"null" for Xbox players, getSteamProfileOrBot will return "BOT" instead of the expected "Unknown [XBOX]" shown elsewhere in the codebase.

Consider adding Xbox handling similar to StationMarker.tsx:

Suggested approach
 	useEffect(() => {
-		if (selectedTrain)
-			getSteamProfileOrBot(selectedTrain.TrainData.ControlledBySteamID).then( // support xbox players
-				// @ts-ignore
-				setData,
-			);
+		if (selectedTrain) {
+			if (selectedTrain.TrainData.ControlledBySteamID !== "null") {
+				getSteamProfileOrBot(selectedTrain.TrainData.ControlledBySteamID).then(
+					// @ts-ignore
+					setData,
+				);
+			} else if (selectedTrain.TrainData.ControlledByXboxID !== "null") {
+				setAvatar(null);
+				setUsername("Unknown [XBOX]");
+			}
+		}
 	}, [selectedTrain]);
📝 Committable suggestion

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

Suggested change
if (selectedTrain)
getSteamProfileOrBot(selectedTrain.TrainData.ControlledBySteamID).then(
getSteamProfileOrBot(selectedTrain.TrainData.ControlledBySteamID).then( // support xbox players
// @ts-ignore
setData,
);
useEffect(() => {
if (selectedTrain) {
if (selectedTrain.TrainData.ControlledBySteamID !== "null") {
getSteamProfileOrBot(selectedTrain.TrainData.ControlledBySteamID).then(
// @ts-ignore
setData,
);
} else if (selectedTrain.TrainData.ControlledByXboxID !== "null") {
setAvatar(null);
setUsername("Unknown [XBOX]");
}
}
}, [selectedTrain]);

Comment on lines 61 to 80
if (trains[i].Type === "user" && trains[i] != null) {
if (trains[i].TrainData.ControlledBySteamID != "null") {
userIDs.push(trains[i].TrainData.ControlledBySteamID);
platforms.push("steam");
}
else if (trains[i].TrainData.ControlledByXboxID != "null") {
userIDs.push(trains[i].TrainData.ControlledByXboxID);
platforms.push("xbox");
}
}
}
for (let i = 0; i < stations.length; i++) {
if (stations[i].DispatchedBy[0])
if (stations[i].DispatchedBy[0] && stations[i].DispatchedBy[0].SteamId != null) {
userIDs.push(stations[i].DispatchedBy[0].SteamId);
platforms.push("steam");
}
if (stations[i].DispatchedBy[0] && stations[i].DispatchedBy[0].XboxId != null) {
userIDs.push(stations[i].DispatchedBy[0].XboxId);
platforms.push("xbox");
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Inconsistent null checking between trains and stations.

Trains check against the string "null" (lines 62, 66), but stations check against actual null (lines 73, 77). Per the PR description mentioning "Change to string null to match api", if the API returns the string "null" for stations as well, these checks will fail to filter correctly.

Also, the pipeline flagged != usage—use !== for strict equality.

Proposed fix for consistent null handling
 		for (let i = 0; i < trains.length; i++) {
 			if (trains[i].Type === "user" && trains[i] != null) {
-				if (trains[i].TrainData.ControlledBySteamID != "null") {
+				if (trains[i].TrainData.ControlledBySteamID !== "null") {
 					userIDs.push(trains[i].TrainData.ControlledBySteamID);
 					platforms.push("steam");
 				}
-				else if (trains[i].TrainData.ControlledByXboxID != "null") {
+				else if (trains[i].TrainData.ControlledByXboxID !== "null") {
 					userIDs.push(trains[i].TrainData.ControlledByXboxID);
 					platforms.push("xbox");
 				}
 			}
 		}
 		for (let i = 0; i < stations.length; i++) {
-			if (stations[i].DispatchedBy[0] && stations[i].DispatchedBy[0].SteamId != null) {
+			if (stations[i].DispatchedBy[0]?.SteamId && stations[i].DispatchedBy[0].SteamId !== "null") {
 				userIDs.push(stations[i].DispatchedBy[0].SteamId);
 				platforms.push("steam");
-			}
-			if (stations[i].DispatchedBy[0] && stations[i].DispatchedBy[0].XboxId != null) {
+			} else if (stations[i].DispatchedBy[0]?.XboxId && stations[i].DispatchedBy[0].XboxId !== "null") {
 				userIDs.push(stations[i].DispatchedBy[0].XboxId);
 				platforms.push("xbox");
 			}
 		}
📝 Committable suggestion

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

Suggested change
if (trains[i].Type === "user" && trains[i] != null) {
if (trains[i].TrainData.ControlledBySteamID != "null") {
userIDs.push(trains[i].TrainData.ControlledBySteamID);
platforms.push("steam");
}
else if (trains[i].TrainData.ControlledByXboxID != "null") {
userIDs.push(trains[i].TrainData.ControlledByXboxID);
platforms.push("xbox");
}
}
}
for (let i = 0; i < stations.length; i++) {
if (stations[i].DispatchedBy[0])
if (stations[i].DispatchedBy[0] && stations[i].DispatchedBy[0].SteamId != null) {
userIDs.push(stations[i].DispatchedBy[0].SteamId);
platforms.push("steam");
}
if (stations[i].DispatchedBy[0] && stations[i].DispatchedBy[0].XboxId != null) {
userIDs.push(stations[i].DispatchedBy[0].XboxId);
platforms.push("xbox");
}
if (trains[i].Type === "user" && trains[i] != null) {
if (trains[i].TrainData.ControlledBySteamID !== "null") {
userIDs.push(trains[i].TrainData.ControlledBySteamID);
platforms.push("steam");
}
else if (trains[i].TrainData.ControlledByXboxID !== "null") {
userIDs.push(trains[i].TrainData.ControlledByXboxID);
platforms.push("xbox");
}
}
}
for (let i = 0; i < stations.length; i++) {
if (stations[i].DispatchedBy[0]?.SteamId && stations[i].DispatchedBy[0].SteamId !== "null") {
userIDs.push(stations[i].DispatchedBy[0].SteamId);
platforms.push("steam");
} else if (stations[i].DispatchedBy[0]?.XboxId && stations[i].DispatchedBy[0].XboxId !== "null") {
userIDs.push(stations[i].DispatchedBy[0].XboxId);
platforms.push("xbox");
}
🧰 Tools
🪛 GitHub Actions: Code quality

[warning] 62-62: lint/suspicious/noDoubleEquals: Use !== instead of !=


[warning] 66-66: lint/suspicious/noDoubleEquals: Use !== instead of !=

🤖 Prompt for AI Agents
In @packages/map/components/SpotlightSearch.tsx around lines 61 - 80, The checks
are inconsistent: trains compare ControlledBySteamID/ControlledByXboxID to the
string "null" using !=, but stations compare DispatchedBy[0].SteamId/XboxId to
actual null; update station checks to mirror trains by first ensuring
DispatchedBy exists and has an element (e.g., DispatchedBy &&
DispatchedBy.length > 0) and then compare SteamId/XboxId to the string "null"
using strict inequality (!== "null"); also replace any remaining != with !== for
ControlledBySteamID/ControlledByXboxID comparisons to use strict equality
consistently in the code handling trains and stations (references: trains,
trains[i].TrainData.ControlledBySteamID, ControlledByXboxID, stations,
stations[i].DispatchedBy, SteamId, XboxId).

}

getUsernames(userIDs);
getUsernames(userIDs, platforms);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing await causes race condition with username resolution.

getUsernames is async but not awaited. The spotlight actions are built immediately after (lines 85-140), before the cache is populated. This causes usernames to display as "Unknown" until the next render cycle.

Proposed fix
-		getUsernames(userIDs, platforms);
+		getUsernames(userIDs, platforms).then(() => {
+			// Move action building logic here, or trigger a re-render
+		});

Alternatively, consider restructuring the effect to properly await the usernames before building actions, or split into separate effects.

🤖 Prompt for AI Agents
In @packages/map/components/SpotlightSearch.tsx at line 83, The effect calls
getUsernames(userIDs, platforms) but does not await it, causing a race where
spotlight actions are built before the username cache is populated; modify the
effect to await getUsernames(...) (or refactor to separate effects) so usernames
are resolved before constructing the actions in
buildSpotlightActions/buildActions (or whatever function builds the actions),
ensuring the async call to getUsernames is awaited and errors handled so the
actions use populated usernames instead of "Unknown".

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
packages/map/components/SpotlightSearch.tsx (1)

39-50: Async function called without await — cache may not be populated before rendering.

getUsernames is an async function, but it's called without await at line 83. This means the spotlight actions are built before the usernames cache is populated, resulting in "Unknown" being displayed until the next render cycle.

Suggested fix

Wrap the effect body in an async IIFE or extract to an async function that's properly awaited:

 	// biome-ignore lint/correctness/useExhaustiveDependencies:
 	useEffect(() => {
+		(async () => {
 		const actionsGroups: SpotlightActionGroupData[] = [];

 		if (!open) return;

 		const userIDs = [];
 		const platforms = [];
 		// ... collection logic ...

-		getUsernames(userIDs, platforms);
+		await getUsernames(userIDs, platforms);

 		// ... rendering logic ...

 		setSpotlightActions(actionsGroups);
+		})();
 	}, [trains, stations, open, map?.panTo, setSelectedTrain, map?.setZoom]);

Also applies to: 83-83

🤖 Fix all issues with AI agents
In @packages/map/components/SpotlightSearch.tsx:
- Around line 119-128: Use optional chaining on station.DispatchedBy and its IDs
and make the checks exclusive so Steam usernames take precedence over Xbox like
train handling: in the SpotlightSearch component, replace the two separate ifs
that inspect station.DispatchedBy[0].SteamId and .XboxId with guarded
optional-chaining checks (e.g., station.DispatchedBy?.[0]?.SteamId) and use an
if / else if (or early-return) so the SteamId branch sets username from
usernamesCache.current.get(…) ?? "Unknown" and only if SteamId is absent fall
back to the Xbox branch (which can call or be replaced by a helper to resolve
Xbox usernames).
🧹 Nitpick comments (3)
packages/map/components/SpotlightSearch.tsx (3)

15-24: Class name should use PascalCase.

The class user should be renamed to User to follow TypeScript/JavaScript naming conventions for classes.

Suggested fix
-class user {
+class User {
 	username = "";
 	ID = "";
 	platform = "";
-	constructor(username: string, ID: string, platform: string) {
+	constructor(username: string, id: string, platform: string) {
 		this.username = username;
-		this.ID = ID;
+		this.ID = id;
 		this.platform = platform;
 	}
 }
-export const usernames: user[] = [];
+export const usernames: User[] = [];

60-71: Null check is in the wrong order and redundant.

At line 61, trains[i].Type is accessed before the null check trains[i] != null. If trains[i] were null, it would throw before reaching the null check. Given trains is typed as Train[], the null check is likely unnecessary.

Suggested simplification
 		for (let i = 0; i < trains.length; i++) {
-			if (trains[i].Type === "user" && trains[i] != null) {
+			if (trains[i].Type === "user") {
 				if (trains[i].TrainData.ControlledBySteamID) {
 					userIDs.push(trains[i].TrainData.ControlledBySteamID);
 					platforms.push("steam");
 				}
 				else if (trains[i].TrainData.ControlledByXboxID) {
 					userIDs.push(trains[i].TrainData.ControlledByXboxID);
 					platforms.push("xbox");
 				}
 			}
 		}

96-98: LGTM with TODO noted.

The placeholder "Unknown [XBOX]" is acceptable for initial Xbox support. The TODO comment references a potential endpoint for Xbox username resolution.

Would you like me to open an issue to track implementing Xbox username resolution via the https://panel.simrail.eu:8084/users-open/ endpoint?

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dbe8d69 and cac3a24.

📒 Files selected for processing (1)
  • packages/map/components/SpotlightSearch.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
packages/map/components/SpotlightSearch.tsx (1)
packages/map/components/steam.ts (1)
  • getSteamProfileOrBot (8-16)
🪛 GitHub Actions: Code quality
packages/map/components/SpotlightSearch.tsx

[warning] 119-119: lint/complexity/useOptionalChain: Change to an optional chain.


[warning] 125-125: lint/complexity/useOptionalChain: Change to an optional chain.

🔇 Additional comments (1)
packages/map/components/SpotlightSearch.tsx (1)

72-81: Inconsistent platform handling between trains and stations.

For trains (lines 62-69), Steam and Xbox are mutually exclusive (if/else if), but for stations both IDs can be collected if present. Consider whether this asymmetry is intentional.

Comment on lines +119 to +128
if (station.DispatchedBy[0] && station.DispatchedBy[0].SteamId) {
username =
usernamesCache.current.get(station.DispatchedBy[0].SteamId) ??
"Unknown";
}

if (station.DispatchedBy[0] && station.DispatchedBy[0].XboxId) {
username = "Unknown [XBOX]" // make function for getting xbox usernames https://panel.simrail.eu:8084/users-open/
}

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use optional chaining and clarify precedence logic.

The pipeline warnings indicate these conditions should use optional chaining. Additionally, since both if statements are not exclusive, Xbox will override Steam if both IDs exist — this differs from train handling where Steam takes precedence.

Fix for optional chaining and consistent precedence
-				if (station.DispatchedBy[0] && station.DispatchedBy[0].SteamId) {
+				if (station.DispatchedBy[0]?.SteamId) {
 					username =
 						usernamesCache.current.get(station.DispatchedBy[0].SteamId) ??
 						"Unknown";
 				}

-				if (station.DispatchedBy[0] && station.DispatchedBy[0].XboxId) {
+				else if (station.DispatchedBy[0]?.XboxId) {
 					username = "Unknown [XBOX]" // make function for getting xbox usernames https://panel.simrail.eu:8084/users-open/
 				}
📝 Committable suggestion

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

Suggested change
if (station.DispatchedBy[0] && station.DispatchedBy[0].SteamId) {
username =
usernamesCache.current.get(station.DispatchedBy[0].SteamId) ??
"Unknown";
}
if (station.DispatchedBy[0] && station.DispatchedBy[0].XboxId) {
username = "Unknown [XBOX]" // make function for getting xbox usernames https://panel.simrail.eu:8084/users-open/
}
if (station.DispatchedBy[0]?.SteamId) {
username =
usernamesCache.current.get(station.DispatchedBy[0].SteamId) ??
"Unknown";
}
else if (station.DispatchedBy[0]?.XboxId) {
username = "Unknown [XBOX]" // make function for getting xbox usernames https://panel.simrail.eu:8084/users-open/
}
🧰 Tools
🪛 GitHub Actions: Code quality

[warning] 119-119: lint/complexity/useOptionalChain: Change to an optional chain.


[warning] 125-125: lint/complexity/useOptionalChain: Change to an optional chain.

🤖 Prompt for AI Agents
In @packages/map/components/SpotlightSearch.tsx around lines 119 - 128, Use
optional chaining on station.DispatchedBy and its IDs and make the checks
exclusive so Steam usernames take precedence over Xbox like train handling: in
the SpotlightSearch component, replace the two separate ifs that inspect
station.DispatchedBy[0].SteamId and .XboxId with guarded optional-chaining
checks (e.g., station.DispatchedBy?.[0]?.SteamId) and use an if / else if (or
early-return) so the SteamId branch sets username from
usernamesCache.current.get(…) ?? "Unknown" and only if SteamId is absent fall
back to the Xbox branch (which can call or be replaced by a helper to resolve
Xbox usernames).

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.

1 participant