Clan Game History#3988
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds paginated clan game history and schemas, replaces eager clan-stats fetching with lazy loads, integrates a game-history tab in ClanModal, refactors ClanDetailView to lazy-load members, updates duration formatting and localization, and adapts tests/mocks. ChangesClan Game History Feature
Sequence Diagram(s)sequenceDiagram
participant Modal as ClanModal
participant View as ClanGameHistoryView
participant API as fetchClanGames
participant Observer as IntersectionObserver
Modal->>View: render(clanTag, cachedState)
View->>API: fetchClanGames(tag, filter?, cursor?)
API-->>View: ClanGamesResponse | { error: "forbidden" | "failed" }
View->>View: append results, group by day
Observer->>View: sentinel visible -> fetch next cursor
View->>Modal: dispatch history-updated(tag, filter, games, nextCursor)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Adds a new “Game History” experience to the clan modal, including client-side API/schema support and UI for browsing and replaying recent clan games, while removing the legacy clan stats fetch/render path.
Changes:
- Introduces
fetchClanGames()+ new Zod schemas/types for clan game history payloads. - Adds a new
ClanGameHistoryViewtab toClanModal, with cursor-based infinite scroll and filtering. - Adjusts supporting UI/utilities (member list lazy-loading behavior, duration formatting, new i18n keys).
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/client/clan/ClanModalTestUtils.ts | Removes fetchClanStats from the Clan API mock factory. |
| tests/client/clan/ClanModal.rendering.test.ts | Updates rendering test setup to stop mocking fetchClanStats. |
| tests/client/clan/ClanModal.handlers.test.ts | Updates handler tests to stop mocking fetchClanStats. |
| tests/client/clan/ClanApiQueries.test.ts | Removes the fetchClanStats query tests. |
| src/core/ClanApiSchemas.ts | Makes ClanStats.stats optional and adds schemas/types for clan game history responses. |
| src/client/Utils.ts | Enhances renderDuration() formatting to support hours and omit trailing zero units. |
| src/client/components/clan/ClanShared.ts | Removes renderClanWL() (legacy clan stats breakdown container). |
| src/client/components/clan/ClanGameHistoryView.ts | New Lit component implementing the game history UI, filtering, and infinite scroll. |
| src/client/components/clan/ClanDetailView.ts | Defers member list fetch until the Members tab is opened. |
| src/client/ClanModal.ts | Adds detail-view tabs (Overview/Members/Game History) and history cache plumbing. |
| src/client/ClanApi.ts | Removes fetchClanStats() and adds fetchClanGames() with Zod validation + error mapping. |
| resources/lang/en.json | Adds month + game-history related translation keys; removes clan_modal.statistics. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/core/ClanApiSchemas.ts (1)
140-201: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winAdd tests for these core schema changes.
This PR changes
ClanStatsSchemaoptionality and adds several newClanGame*wire cases, but I do not see matching tests in the provided changes. A small parse-focused test matrix for missing/nullstats,playerTeams,totalPlayers, andnextCursorwould make this contract much safer.As per coding guidelines, "All changes to
src/core/must include tests".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/core/ClanApiSchemas.ts` around lines 140 - 201, The PR added optional/nullish fields to core schemas but lacks tests: add unit tests that exercise parsing/validation for ClanStatsSchema (presence and absence/null of stats), ClanGameSchema (playerTeams null vs absent, totalPlayers null vs present), ClanGameResultSchema variants, and ClanGamesResponseSchema (nextCursor null and non-null); create a small matrix of zod.parse assertions (both valid and invalid cases) referencing ClanStatsSchema, ClanGameSchema, ClanGamePlayerSchema, ClanGameResultSchema, and ClanGamesResponseSchema to ensure the wire cases (null, undefined, and real values) round-trip as expected.src/client/components/clan/ClanGameHistoryView.ts (1)
1-665:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRun Prettier on this file before merge.
CI is already red on
src/client/components/clan/ClanGameHistoryView.tsfor formatting, so this will not merge cleanly as-is.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/client/components/clan/ClanGameHistoryView.ts` around lines 1 - 665, The file ClanGameHistoryView.ts is failing CI due to formatting; run Prettier on this file (or your repo's formatter script) and commit the resulting changes so the export/class/function spacing, template literal indentation, and trailing commas match project style; specifically reformat the clan-game-history-view LitElement class and helper functions (ClanGameHistoryView, formatAbsoluteTime, groupGamesByDay, dayKey, formatDayHeader) and ensure no lint/format errors remain before pushing.src/client/ClanModal.ts (1)
190-198:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPreserve the previous list tab when leaving detail.
openDetail()always overwritesactiveTabwith"overview", and every exit path hardcodes"my-clans". Opening a clan from Browse now sends users back to My Clans, so they lose their browse context and cached filters.♻️ Minimal fix sketch
+ `@state`() private lastListTab: ListTab = "my-clans"; + protected onTabEnter(tab: string): void { if (isListTab(tab)) { + this.lastListTab = tab; this.view = "list"; this.selectedClan = null; this.selectedClanTag = ""; if (tab === "my-clans") { this.loadMyClans(); @@ onBack: () => { this.view = "list"; this.selectedClan = null; this.selectedClanTag = ""; this.myRole = null; this.detailCache = null; this.gameHistoryCache = null; - this.activeTab = "my-clans"; + this.activeTab = this.lastListTab; }, @@ private openDetail(tag: string) { + if (isListTab(this.activeTab)) { + this.lastListTab = this.activeTab; + } if (this.selectedClanTag !== tag) { this.gameHistoryCache = null; } this.selectedClanTag = tag; this.view = "detail"; this.activeTab = "overview"; }Also applies to: 304-305, 366-374, 422-432, 464-472
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/client/ClanModal.ts` around lines 190 - 198, openDetail() and multiple exit paths overwrite activeTab to "overview" or hardcode "my-clans", which loses the user's previous list/browse tab; fix by preserving and restoring the prior tab: before changing this.view to "detail" in openDetail(), save the current this.activeTab into a new property like this.previousActiveTab (or only set this.activeTab to "overview" if it is empty), and on all exit branches (the onBack handler and other detail-exit code paths that currently set this.view = "list") restore this.activeTab = this.previousActiveTab (or leave it unchanged) instead of assigning "my-clans"; update references in openDetail(), onBack, and the other exit handlers (the methods that clear selectedClan/selectedClanTag/myRole/detailCache/gameHistoryCache) to use this.previousActiveTab for restoration.
🧹 Nitpick comments (1)
src/client/components/clan/ClanDetailView.ts (1)
154-175: ⚡ Quick winRemove the unreachable role fallback.
loadInitialMembers()exits unlessmyClanRoles.has(this.clanTag)is true, soknownRoleon Lines 171-175 cannot be missing anymore. Either delete that branch or relax the earlier guard if this fetch is supposed to discover the current user's role.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/client/components/clan/ClanDetailView.ts` around lines 154 - 175, The post-fetch fallback that sets this.myRole based on fetched members is unreachable because loadInitialMembers() returns early when this.myClanRoles.has(this.clanTag) is false; either remove the unreachable branch (the knownRole check and the block that finds me by this.myPublicId and sets this.myRole) or instead relax the early guard (remove or change the if (!this.myClanRoles.has(this.clanTag)) return) so the fetch can run to discover the current user's role; update the loadInitialMembers method accordingly and keep references to myClanRoles, clanTag, knownRole, myPublicId, and myRole in your change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/client/ClanModal.ts`:
- Around line 39-43: The helper isDetailTab is unused and causing an ESLint
error; either remove the function or use it where tab keys are validated—update
the tab-routing or validation logic (the same place isListTab and LIST_TABS are
used) to call isDetailTab(key) when you expect a DetailTab, or delete the
isDetailTab function and its DETAIL_TABS reference if detail-tab validation is
handled elsewhere; ensure consistent use of isListTab, isDetailTab, LIST_TABS
and DETAIL_TABS in the tab switch/validation logic so no unused helper remains.
In `@src/client/components/clan/ClanGameHistoryView.ts`:
- Around line 531-550: The current renderPlayersField incorrectly treats Free
For All games as a single-clan slot so it shows only total player count; update
the isSingleClanSlot logic in renderPlayersField (used with renderField and
related to game.mode, game.rankedType, and clanPlayers) to NOT treat game.mode
=== "Free For All" as a single clan slot (i.e., only consider
rankedType/unranked for single-clan behavior) so that FFA rows render
clanPlayers.length (and thus keep renderResultBadge behavior intact) when
appropriate.
- Around line 44-81: The current hydration logic lives only in
connectedCallback, so move that "hydrate from cache vs reload" behavior into
updated(changed) and trigger it when changed.has('clanTag') or
changed.has('cachedState'); before hydrating or calling reload(), increment
asyncGeneration to cancel in-flight requests and reset loading = false,
loadingMore = false, appendFailed = false, and loadState = "ok"; if cachedState
matches clanTag restore games, nextCursor, filter from cachedState, else call
reload(); keep
connectedCallback/disconnectedCallback/ensureObserver/teardownObserver as-is but
remove the hydration logic from connectedCallback so updates while mounted
behave correctly (refer to properties clanTag, cachedState and fields games,
nextCursor, filter, asyncGeneration, loading, loadingMore, appendFailed,
loadState and method reload()).
In `@src/client/Utils.ts`:
- Around line 233-245: The renderDuration function currently emits hardcoded
"h", "min", and "s" suffixes; change it to call translateText() for each unit
(e.g. keys like "duration.hour", "duration.minute", "duration.second") and use
those returned strings when building parts in renderDuration so localization
flows through translateText; also add matching entries to resources/lang/en.json
for those keys (providing "h", "min", "s" or full words as desired) and ensure
translateText is imported/available in Utils.ts before using it.
In `@src/core/ClanApiSchemas.ts`:
- Around line 193-199: Update the comment on nextCursor in the
ClanGamesResponseSchema to reflect that it is an opaque continuation token (to
be round-tripped verbatim as the cursor query parameter) rather than an ISO
datetime or a "before" timestamp; modify the comment text near the nextCursor
field in ClanGamesResponseSchema to mention "opaque continuation token" and
"cursor" so it matches the client/API contract, without changing the
z.string().nullable() validator or schema shape.
---
Outside diff comments:
In `@src/client/ClanModal.ts`:
- Around line 190-198: openDetail() and multiple exit paths overwrite activeTab
to "overview" or hardcode "my-clans", which loses the user's previous
list/browse tab; fix by preserving and restoring the prior tab: before changing
this.view to "detail" in openDetail(), save the current this.activeTab into a
new property like this.previousActiveTab (or only set this.activeTab to
"overview" if it is empty), and on all exit branches (the onBack handler and
other detail-exit code paths that currently set this.view = "list") restore
this.activeTab = this.previousActiveTab (or leave it unchanged) instead of
assigning "my-clans"; update references in openDetail(), onBack, and the other
exit handlers (the methods that clear
selectedClan/selectedClanTag/myRole/detailCache/gameHistoryCache) to use
this.previousActiveTab for restoration.
In `@src/client/components/clan/ClanGameHistoryView.ts`:
- Around line 1-665: The file ClanGameHistoryView.ts is failing CI due to
formatting; run Prettier on this file (or your repo's formatter script) and
commit the resulting changes so the export/class/function spacing, template
literal indentation, and trailing commas match project style; specifically
reformat the clan-game-history-view LitElement class and helper functions
(ClanGameHistoryView, formatAbsoluteTime, groupGamesByDay, dayKey,
formatDayHeader) and ensure no lint/format errors remain before pushing.
In `@src/core/ClanApiSchemas.ts`:
- Around line 140-201: The PR added optional/nullish fields to core schemas but
lacks tests: add unit tests that exercise parsing/validation for ClanStatsSchema
(presence and absence/null of stats), ClanGameSchema (playerTeams null vs
absent, totalPlayers null vs present), ClanGameResultSchema variants, and
ClanGamesResponseSchema (nextCursor null and non-null); create a small matrix of
zod.parse assertions (both valid and invalid cases) referencing ClanStatsSchema,
ClanGameSchema, ClanGamePlayerSchema, ClanGameResultSchema, and
ClanGamesResponseSchema to ensure the wire cases (null, undefined, and real
values) round-trip as expected.
---
Nitpick comments:
In `@src/client/components/clan/ClanDetailView.ts`:
- Around line 154-175: The post-fetch fallback that sets this.myRole based on
fetched members is unreachable because loadInitialMembers() returns early when
this.myClanRoles.has(this.clanTag) is false; either remove the unreachable
branch (the knownRole check and the block that finds me by this.myPublicId and
sets this.myRole) or instead relax the early guard (remove or change the if
(!this.myClanRoles.has(this.clanTag)) return) so the fetch can run to discover
the current user's role; update the loadInitialMembers method accordingly and
keep references to myClanRoles, clanTag, knownRole, myPublicId, and myRole in
your change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7e757e09-5c3d-4646-91b4-5d9225dc6d4d
📒 Files selected for processing (12)
resources/lang/en.jsonsrc/client/ClanApi.tssrc/client/ClanModal.tssrc/client/Utils.tssrc/client/components/clan/ClanDetailView.tssrc/client/components/clan/ClanGameHistoryView.tssrc/client/components/clan/ClanShared.tssrc/core/ClanApiSchemas.tstests/client/clan/ClanApiQueries.test.tstests/client/clan/ClanModal.handlers.test.tstests/client/clan/ClanModal.rendering.test.tstests/client/clan/ClanModalTestUtils.ts
💤 Files with no reviewable changes (3)
- tests/client/clan/ClanModalTestUtils.ts
- src/client/components/clan/ClanShared.ts
- tests/client/clan/ClanApiQueries.test.ts
There was a problem hiding this comment.
🧹 Nitpick comments (1)
tests/client/clan/ClanApiSchemas.test.ts (1)
384-388: ⚡ Quick winUse an opaque token fixture for
nextCursortests.At Line 387, using an ISO timestamp can blur the cursor contract. A non-date token makes it clear this value is opaque and only round-tripped.
Proposed test tweak
it("accepts a non-empty page with a cursor", () => { const result = ClanGamesResponseSchema.safeParse({ results: [validGame], - nextCursor: "2024-05-31T00:00:00.000Z", + nextCursor: "cursor_v1:eyJpZCI6MTIzfQ", }); expect(result.success).toBe(true); });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/client/clan/ClanApiSchemas.test.ts` around lines 384 - 388, The test "accepts a non-empty page with a cursor" uses an ISO timestamp for nextCursor which obscures the cursor-as-opaque-token contract; update the test that calls ClanGamesResponseSchema.safeParse (the one using validGame) to use a non-date opaque token (e.g. "opaque-cursor-<unique>") or a dedicated cursor fixture instead of "2024-05-31T00:00:00.000Z" so the test clearly asserts round-tripping of an opaque cursor value.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@tests/client/clan/ClanApiSchemas.test.ts`:
- Around line 384-388: The test "accepts a non-empty page with a cursor" uses an
ISO timestamp for nextCursor which obscures the cursor-as-opaque-token contract;
update the test that calls ClanGamesResponseSchema.safeParse (the one using
validGame) to use a non-date opaque token (e.g. "opaque-cursor-<unique>") or a
dedicated cursor fixture instead of "2024-05-31T00:00:00.000Z" so the test
clearly asserts round-tripping of an opaque cursor value.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 9ed59b8b-2a0c-4d0a-a045-9ff9bd2c326a
📒 Files selected for processing (8)
resources/lang/en.jsonsrc/client/ClanModal.tssrc/client/Utils.tssrc/client/components/clan/ClanDetailView.tssrc/client/components/clan/ClanGameHistoryView.tssrc/core/ClanApiSchemas.tstests/client/clan/ClanApiQueries.test.tstests/client/clan/ClanApiSchemas.test.ts
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/client/ClanModal.ts (1)
127-135:⚠️ Potential issue | 🟠 Major | ⚡ Quick winClear
detailCachewhen routing back to a list tab.This branch drops
selectedClanbut keepsdetailCache. If the user reaches"browse"/"my-clans"through tab routing and then reopens the same clan,clan-detail-viewrestores cached members withcachedClan = null, skipsloadDetail(), and the detail page renders blank.💡 Minimal fix
protected onTabEnter(tab: string): void { if (isListTab(tab)) { this.view = "list"; this.selectedClan = null; this.selectedClanTag = ""; + this.myRole = null; + this.detailCache = null; + this.gameHistoryCache = null; if (tab === "my-clans") { this.loadMyClans(); } return; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/client/ClanModal.ts` around lines 127 - 135, When switching to a list tab in onTabEnter, also clear the cached detail state so reopening the same clan doesn't reuse stale detailCache; specifically, reset this.detailCache (or whatever property holds cached clan details) alongside this.selectedClan = null and this.selectedClanTag = "" in the isListTab branch (before calling loadMyClans if tab === "my-clans") so clan-detail-view will call loadDetail() instead of restoring from a stale cache.
🧹 Nitpick comments (1)
tests/client/clan/ClanGameHistoryView.test.ts (1)
1-623: ⚡ Quick winConsider using the
flushAsynchelper fromClanModalTestUtilsfor more robust async handling.The test file repeatedly uses the pattern:
await new Promise((r) => setTimeout(r, 0)); await el.updateComplete;However,
ClanModalTestUtilsexports aflushAsynchelper that handles this more robustly with double-tick draining for chained microtasks. Using it consistently would reduce duplication and improve reliability, especially for tests involving state updates that trigger re-renders.♻️ Refactor example
Import
flushAsyncat the top:import type { ClanGame, ClanGamesResponse } from "../../../src/client/ClanApi"; import { fetchClanGames } from "../../../src/client/ClanApi"; import { ClanGameHistoryView, type ClanGameHistoryCache, } from "../../../src/client/components/clan/ClanGameHistoryView"; +import { flushAsync } from "./ClanModalTestUtils";Then replace patterns like:
-await new Promise((r) => setTimeout(r, 0)); -await el.updateComplete; +await flushAsync(el);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/client/clan/ClanGameHistoryView.test.ts` around lines 1 - 623, Import the flushAsync helper from ClanModalTestUtils and replace every occurrence of the two-line pattern "await new Promise((r) => setTimeout(r, 0)); await el.updateComplete;" with a single "await flushAsync();" (keeping any following awaits of el.updateComplete only if still needed) so async microtask draining is handled robustly when waiting for ClanGameHistoryView render/update cycles that rely on el.updateComplete.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/client/ClanModal.ts`:
- Around line 127-135: When switching to a list tab in onTabEnter, also clear
the cached detail state so reopening the same clan doesn't reuse stale
detailCache; specifically, reset this.detailCache (or whatever property holds
cached clan details) alongside this.selectedClan = null and this.selectedClanTag
= "" in the isListTab branch (before calling loadMyClans if tab === "my-clans")
so clan-detail-view will call loadDetail() instead of restoring from a stale
cache.
---
Nitpick comments:
In `@tests/client/clan/ClanGameHistoryView.test.ts`:
- Around line 1-623: Import the flushAsync helper from ClanModalTestUtils and
replace every occurrence of the two-line pattern "await new Promise((r) =>
setTimeout(r, 0)); await el.updateComplete;" with a single "await flushAsync();"
(keeping any following awaits of el.updateComplete only if still needed) so
async microtask draining is handled robustly when waiting for
ClanGameHistoryView render/update cycles that rely on el.updateComplete.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 65b58c99-5e4b-40a0-9529-0bc31216b939
📒 Files selected for processing (9)
src/client/ClanApi.tssrc/client/ClanModal.tssrc/client/Utils.tssrc/client/components/clan/ClanDetailView.tssrc/client/components/clan/ClanGameHistoryView.tssrc/core/ClanApiSchemas.tstests/client/clan/ClanApiSchemas.test.tstests/client/clan/ClanGameHistoryView.test.tstests/client/clan/ClanModalTestUtils.ts
💤 Files with no reviewable changes (2)
- src/client/ClanApi.ts
- tests/client/clan/ClanApiSchemas.test.ts
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/client/clan/ClanGameHistoryView.test.ts`:
- Around line 176-177: The test calls mountView({ cachedState: cache }) but
doesn't capture its return and then calls flushAsync() with no argument, which
is inconsistent with other tests; update the test to capture the mounted element
(const el = await mountView({ cachedState: cache });) and then call
flushAsync(el) so it matches the pattern used elsewhere and ensures flushAsync
receives the correct element, or alternatively confirm and document that
flushAsync is intentionally argument-optional if you prefer that behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 0896aacd-6bb3-4045-9de0-d0e23bbaa0da
📒 Files selected for processing (2)
src/client/ClanModal.tstests/client/clan/ClanGameHistoryView.test.ts
Description:
Adds

continuation of infra https://github.com/openfrontio/infra/pull/345
Please complete the following:
Please put your Discord username so you can be contacted if a bug or regression is found:
w.o.n