Skip to content

Clansystem#3517

Open
ryanbarlow97 wants to merge 6 commits intomainfrom
clansystem
Open

Clansystem#3517
ryanbarlow97 wants to merge 6 commits intomainfrom
clansystem

Conversation

@ryanbarlow97
Copy link
Copy Markdown
Contributor

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

Description:

Describe the PR.

Please complete the following:

  • I have added screenshots for all UI updates
  • I process any text displayed to the user through translateText() and I've added it to the en.json file
  • I have added relevant tests to the test directory
  • I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced

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

DISCORD_USERNAME

Copilot AI review requested due to automatic review settings March 26, 2026 17:52
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 26, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a3a8ff79-a735-4097-a106-93d463c0f5e5

📥 Commits

Reviewing files that changed from the base of the PR and between 571e0a8 and 6774b78.

📒 Files selected for processing (1)
  • src/client/ClanModal.ts
✅ Files skipped from review due to trivial changes (1)
  • src/client/ClanModal.ts

Walkthrough

Added a new Clan UI: a Lit-based <clan-modal> component plus translations and navigation entries. The modal implements browsing, creating, membership and leadership workflows with multiple views/tabs, search, and pagination; the component is registered on client bootstrap and mounted in the page markup.

Changes

Cohort / File(s) Summary
Clan Modal Component
src/client/ClanModal.ts
New Lit custom element clan-modal implementing clan browsing, create, detail, manage, transfer, and requests views; includes search, member lists, role actions, pagination, and view state resets. (+~1400 lines)
Navigation Integration
src/client/components/DesktopNavBar.ts, src/client/components/MobileNavBar.ts
Added data-page="page-clan" nav buttons with data-i18n="main.clans" matching existing active-state behavior for desktop and mobile.
DOM Mount & Module Load
index.html, src/client/Main.ts
Inserted <clan-modal id="page-clan" inline class="hidden w-full h-full page-content relative z-50"></clan-modal> in page DOM and imported ./ClanModal in client bootstrap to register the component.
Translations
resources/lang/en.json
Added main.clans label and a new clan_modal namespace containing UI strings for titles, tabs, clan fields, member actions, transfer, join requests, search, pagination, and confirmations. (+48 lines)

Sequence Diagram(s)

mermaid
sequenceDiagram
participant User as "User"
participant Nav as "Desktop/Mobile NavBar"
participant Client as "Main (bootstrap)"
participant Modal as "ClanModal"
User->>Nav: click "Clans" (data-page="page-clan")
Nav->>Client: notify page change
Client->>Modal: open modal (show, initialize view)
Modal->>Modal: load local dataset / apply search & pagination
User->>Modal: interact (browse/create/manage/transfer/requests)
Modal->>Modal: update view state (selectedClan, tab, page, filters)
Modal->>User: render updated UI / show confirmations

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🏰 A modal opens for clans to meet,
Tabs and lists and members greet,
Search, promote, and transfer care,
Click the nav and enter there—
New fellowship now takes its seat.

🚥 Pre-merge checks | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is an incomplete template with placeholder text and empty sections, providing no meaningful information about the implementation or changes. Complete the PR template by describing the clan system implementation, confirming all checklist items are met, and providing your Discord username for contact.
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.
Title check ❓ Inconclusive The title 'Clansystem' is vague and generic, using a single word that doesn't convey specific details about what changes were made or why. Use a more descriptive title like 'Add clan system with modal, navigation, and translations' to clearly communicate the scope and purpose of the changes.

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


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

❤️ Share

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

Copy link
Copy Markdown
Contributor

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

Adds a new “Clans” destination to the client navigation and introduces a new clan-modal page to present a clan/browse/manage UI (currently using mock data), along with required English translations.

Changes:

  • Added a new “Clans” nav item to both mobile and desktop nav bars that navigates to page-clan.
  • Introduced ClanModal (<clan-modal>) and wired it into the client entrypoint + index.html as a new inline page.
  • Added main.clans and clan_modal.* translation keys to resources/lang/en.json.

Reviewed changes

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

Show a summary per file
File Description
src/client/components/MobileNavBar.ts Adds a new nav button targeting page-clan.
src/client/components/DesktopNavBar.ts Adds a new nav button targeting page-clan.
src/client/Main.ts Imports the new ClanModal so the custom element is registered.
src/client/ClanModal.ts Adds the new clans UI/modal implementation (currently backed by fake data).
resources/lang/en.json Adds “Clans” nav label and clan modal translation keys.
index.html Adds the new inline <clan-modal id="page-clan"> page container.

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

Comment on lines +930 to +943
<button
@click=${() => (this.manageIsOpen = !this.manageIsOpen)}
class="relative w-12 h-7 rounded-full transition-all ${this
.manageIsOpen
? "bg-blue-500"
: "bg-white/20"}"
>
<div
class="absolute top-1 w-5 h-5 rounded-full bg-white shadow transition-all ${this
.manageIsOpen
? "left-6"
: "left-1"}"
></div>
</button>
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

The open/closed toggle is implemented as a plain <button> with only visual state. For accessibility, expose state via aria-pressed (or role="switch" + aria-checked) and provide an aria-label so screen readers can identify what is being toggled.

Copilot uses AI. Check for mistakes.

private renderMemberPagination(totalMembers: number) {
const totalPages = Math.ceil(totalMembers / this.membersPerPage);
if (totalMembers <= this.perPageOptions[0]) return html``;
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

renderMemberPagination() hides pagination based on totalMembers <= 10, which is unrelated to the current membersPerPage selection and can still render pagination when totalPages === 1 (e.g., 30 members with 50 per page). Use totalPages <= 1 (or totalMembers <= this.membersPerPage) instead.

Suggested change
if (totalMembers <= this.perPageOptions[0]) return html``;
if (totalPages <= 1) return html``;

Copilot uses AI. Check for mistakes.
Comment on lines +35 to +83
const FAKE_MEMBER_NAMES = [
"GrandMaster42",
"Lieutenant_X",
"BattleHawk",
"NovaStar",
"IronWill",
"StormChaser",
"PhantomBlade",
"CrimsonTide",
"FrostNova",
"ShadowFang",
"ThunderStrike",
"VoidWalker",
"SkyRaider",
"IronClad",
"BlazeFury",
"NightHawk",
"StormBreaker",
"DarkMatter",
"SolarFlare",
"GhostReaper",
"WarHound",
"DeathStroke",
"IcePhoenix",
"FireDragon",
"WolfBane",
"SteelViper",
"ThunderBolt",
"ShadowKnight",
"CrimsonWolf",
"BladeRunner",
"StarFall",
"VenomStrike",
"TitanForge",
"RogueAgent",
"ViperKing",
"StormRider",
"FrostBite",
"DarkStar",
"NeonReaper",
"CyberWolf",
"BlitzFire",
"ToxicShark",
"SilverArrow",
"RavenClaw",
"CosmicDust",
"ArcticFox",
"PyroKnight",
];
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

This file ships large hardcoded "fake" datasets (names, clans, members, join requests) directly in the production client bundle, which will noticeably increase bundle size and parse time. Consider moving mock data behind a dev-only flag, loading it dynamically, or replacing it with real API-backed data before merging.

Suggested change
const FAKE_MEMBER_NAMES = [
"GrandMaster42",
"Lieutenant_X",
"BattleHawk",
"NovaStar",
"IronWill",
"StormChaser",
"PhantomBlade",
"CrimsonTide",
"FrostNova",
"ShadowFang",
"ThunderStrike",
"VoidWalker",
"SkyRaider",
"IronClad",
"BlazeFury",
"NightHawk",
"StormBreaker",
"DarkMatter",
"SolarFlare",
"GhostReaper",
"WarHound",
"DeathStroke",
"IcePhoenix",
"FireDragon",
"WolfBane",
"SteelViper",
"ThunderBolt",
"ShadowKnight",
"CrimsonWolf",
"BladeRunner",
"StarFall",
"VenomStrike",
"TitanForge",
"RogueAgent",
"ViperKing",
"StormRider",
"FrostBite",
"DarkStar",
"NeonReaper",
"CyberWolf",
"BlitzFire",
"ToxicShark",
"SilverArrow",
"RavenClaw",
"CosmicDust",
"ArcticFox",
"PyroKnight",
];
const FAKE_MEMBER_NAMES = Array.from({ length: 50 }, (_, index) => `Member${index + 1}`);

Copilot uses AI. Check for mistakes.
Comment on lines +392 to +401
@customElement("clan-modal")
export class ClanModal extends BaseModal {
@state() private activeTab: Tab = "my-clans";
@state() private view: View = "list";
@state() private selectedClan: FakeClan | null = null;
@state() private searchQuery = "";
@state() private createName = "";
@state() private createTag = "";
@state() private createDescription = "";
@state() private createIsOpen = true;
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

There are existing client-side modal tests (e.g., tests/client/LeaderboardModal.test.ts), but this new modal introduces substantial UI/state logic without any automated coverage. Adding at least a minimal test for key flows (tab switching, selecting a clan, pagination/search state) would help prevent regressions.

Copilot uses AI. Check for mistakes.
: ""}
</div>
<div class="flex items-center gap-4 mt-1 text-xs text-white/40">
<span>${clan.members} members</span>
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

This line hardcodes the user-visible word "members" instead of going through the translation system (and it won't pluralize correctly). Use a translated string (ideally ICU pluralization, e.g. "{count, plural, one {...} other {...}}") and pass clan.members as a param.

Suggested change
<span>${clan.members} members</span>
<span>${translateText("clan_modal.members_count", { count: clan.members })}</span>

Copilot uses AI. Check for mistakes.
Comment on lines +1100 to +1106
<div
@click=${() => (this.transferTarget = m.publicId)}
class="flex items-center gap-3 py-3 border-b border-white/5 last:border-0 cursor-pointer rounded-lg px-2 transition-all
${this.transferTarget === m.publicId
? "bg-amber-500/10"
: "hover:bg-white/5"}"
>
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

This list item is a clickable <div> without keyboard support. Interactive selection should be a <button> (preferred) or include role, tabindex, and key handlers (Enter/Space) so it’s accessible via keyboard navigation.

Copilot uses AI. Check for mistakes.
Comment on lines +1481 to +1494
<button
@click=${() => (this.createIsOpen = !this.createIsOpen)}
class="relative w-12 h-7 rounded-full transition-all ${this
.createIsOpen
? "bg-blue-500"
: "bg-white/20"}"
>
<div
class="absolute top-1 w-5 h-5 rounded-full bg-white shadow transition-all ${this
.createIsOpen
? "left-6"
: "left-1"}"
></div>
</button>
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

The create form's open/closed toggle also needs accessible semantics (e.g., aria-pressed/aria-checked and an aria-label) so screen readers can understand and announce its state.

Copilot uses AI. Check for mistakes.
const isLeader = myClan?.role === "leader";
const isMember = !!myClan;
const members = FAKE_MEMBERS[clan.id] ?? [
{ name: this.resolveLeaderName(clan), role: "leader" as const },
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

members falls back to an object with name (not displayName) and no publicId, but later code assumes FakeMember (uses displayName/publicId in search + rendering). This will break if a clan ID is missing from FAKE_MEMBERS (e.g., newly created clans). Make the fallback conform to FakeMember (include publicId + displayName) or remove the fallback and ensure the data source always returns FakeMember[].

Suggested change
{ name: this.resolveLeaderName(clan), role: "leader" as const },
{
displayName: this.resolveLeaderName(clan),
publicId: clan.leaderPublicId,
role: "leader" as const,
},

Copilot uses AI. Check for mistakes.
Comment on lines +854 to +856
const members = FAKE_MEMBERS[clan.id] ?? [
{ name: this.resolveLeaderName(clan), role: "leader" as const },
];
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

Same members fallback issue as in renderClanDetail: the fallback entry doesn't match FakeMember shape (name vs displayName, missing publicId), but downstream code treats it as FakeMember[]. Align the fallback with FakeMember to avoid runtime errors if FAKE_MEMBERS[clan.id] is ever absent.

Copilot uses AI. Check for mistakes.
Comment on lines +1032 to +1040
class="text-[10px] font-bold uppercase tracking-wider px-2 py-0.5 rounded-full shrink-0
${member.role === "leader"
? "bg-amber-500/20 text-amber-400 border border-amber-500/30"
: member.role === "officer"
? "bg-purple-500/20 text-purple-400 border border-purple-500/30"
: "bg-white/10 text-white/40 border border-white/10"}"
>
${member.role}
</span>
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

Role labels are rendered directly from internal values (e.g., "leader", "officer", "member"), which makes them non-localizable and couples UI text to internal enums. Map roles to translation keys (e.g. clan_modal.role_leader) and render translated labels instead.

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

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/client/ClanModal.ts`:
- Around line 617-637: The role chips and member count are hard-coded English;
update the UI to use the i18n helper instead: add keys for role labels (e.g.,
"clan.role.leader", "clan.role.member", etc.) and for the members suffix (e.g.,
"clan.members") in resources/lang/en.json, then replace raw role text and the
"${clan.members} members" rendering with translateText(...) calls — ensure the
role rendering logic in the template that checks the role variable still uses
the same classes and only swaps the displayed text via translateText(roleKey)
and use translateText("clan.members", { count: clan.members }) or equivalent
pluralization path; keep resolveLeaderName(clan) unchanged.
- Around line 1099-1149: The candidate rows rendered in the (m) => html`...`
mapping are non-focusable <div>s and must be made keyboard reachable and expose
selected semantics: replace the clickable container with a real focusable
control (e.g., a <button> or an element with tabindex="0") that uses the same
click handler (set this.transferTarget = m.publicId), add keyboard activation
support (handle Enter/Space to trigger selection), and expose selection state
with aria-selected (aria-selected={this.transferTarget === m.publicId}) or
role="option" inside a role="listbox" context; also ensure a visible focus style
is present when focused. Use the existing transferTarget identifier and the
mapping callback where m.publicId and m.role are referenced to locate and update
the element.
- Around line 35-387: This modal currently uses hardcoded test data (FAKE_CLANS,
FAKE_MY_CLANS, FAKE_MEMBERS, FAKE_JOIN_REQUESTS and generateFakeMembers) causing
the clans page to show fake content; change ClanModal so it no longer reads
those constants directly: either gate the component behind a feature flag (check
a feature flag/hook before rendering and return null or a placeholder if
disabled) or replace direct usage with real data hooks/props (e.g., useClans(),
useMyClans(), useClanMembers(clanId), useJoinRequests(clanId) or accept
clans/myClans/members/joinRequests as props) and remove or scope FAKE_* usage to
a local demo-only path. Ensure all references inside ClanModal to FAKE_CLANS,
FAKE_MY_CLANS, FAKE_MEMBERS, FAKE_JOIN_REQUESTS, and generateFakeMembers are
updated to use the chosen real-data hooks/props or are behind the feature-flag
gate before merging.
- Around line 803-846: The clan action buttons render UI but perform no real
work; either wire them to real handlers or disable/hide them until implemented.
Add and call well-named methods (e.g., handleJoinClan, handleRequestInvite,
handleLeaveClan, handleSaveManage, handlePromoteMember, handleDemoteMember,
handleKickMember, handleConfirmTransfer, handleApproveInvite, handleDenyInvite,
handleDisbandClan, handleCreateClan) from the buttons (and where
manageName/manageDescription/manageIsOpen and view = "manage" are set) to
perform the actual state update + API call and then update component state or
dispatch events with results; if backend flow is not ready, replace interactive
buttons with disabled buttons or hide them by rendering nothing or adding
disabled and tooltip explaining "not implemented". Ensure each handler updates
local state (e.g., isMember/isLeader/clan) and emits a custom event (e.g.,
clan-updated) or calls the existing API layer so the UI reflects changes.
🪄 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: 4449223c-dc5b-4004-916c-5682017a45fe

📥 Commits

Reviewing files that changed from the base of the PR and between 23150f0 and 571e0a8.

📒 Files selected for processing (6)
  • index.html
  • resources/lang/en.json
  • src/client/ClanModal.ts
  • src/client/Main.ts
  • src/client/components/DesktopNavBar.ts
  • src/client/components/MobileNavBar.ts

Comment on lines +35 to +387
const FAKE_MEMBER_NAMES = [
"GrandMaster42",
"Lieutenant_X",
"BattleHawk",
"NovaStar",
"IronWill",
"StormChaser",
"PhantomBlade",
"CrimsonTide",
"FrostNova",
"ShadowFang",
"ThunderStrike",
"VoidWalker",
"SkyRaider",
"IronClad",
"BlazeFury",
"NightHawk",
"StormBreaker",
"DarkMatter",
"SolarFlare",
"GhostReaper",
"WarHound",
"DeathStroke",
"IcePhoenix",
"FireDragon",
"WolfBane",
"SteelViper",
"ThunderBolt",
"ShadowKnight",
"CrimsonWolf",
"BladeRunner",
"StarFall",
"VenomStrike",
"TitanForge",
"RogueAgent",
"ViperKing",
"StormRider",
"FrostBite",
"DarkStar",
"NeonReaper",
"CyberWolf",
"BlitzFire",
"ToxicShark",
"SilverArrow",
"RavenClaw",
"CosmicDust",
"ArcticFox",
"PyroKnight",
];

function generateFakeMembers(count: number, names: string[]): FakeMember[] {
return names.slice(0, count).map((name, i) => ({
publicId: `p_${i.toString(16).padStart(6, "0")}`,
displayName: name,
role: (i === 0
? "leader"
: i < 3
? "officer"
: "member") as FakeMember["role"],
}));
}

const FAKE_MEMBERS: Record<string, FakeMember[]> = {
c1: generateFakeMembers(47, FAKE_MEMBER_NAMES),
c2: generateFakeMembers(32, [
"FrostByte",
"ArcticWind",
"PolarBear",
"IceBreaker",
"SnowDrift",
"TundraWolf",
"GlacierKing",
"Permafrost",
"BlizzardX",
"NorthStar",
"ColdFront",
"WhiteOut",
"FreezeFrame",
"Avalanche",
"IcePick",
"WinterSolstice",
"SubZero",
"HailStorm",
"FrostGiant",
"ChillFactor",
"SnowBlind",
"FrozenSolid",
"IceCap",
"PolarVortex",
"FrostFire",
"ColdSnap",
"Wintermute",
"SleetStorm",
"GlacialPace",
"IceAge",
"FrozenThorn",
"NorthWind",
]),
c3: generateFakeMembers(50, [
"DarkSovereign",
"ShadowLord",
"NightBlade",
"VoidPrince",
"DuskFall",
"MidnightSun",
"EclipseKing",
"PhantomRule",
"GrimReaper",
"OnyxKnight",
"BlackMamba",
"DarkMatter",
"SilentDeath",
"NightCrawler",
"ShadowStep",
"Oblivion",
"DarkVeil",
"TwilightZone",
"AbyssWalker",
"NullVoid",
"DarkForge",
"CryptKeeper",
"DeathMarch",
"NightShade",
"Penumbra",
"UmbraKing",
"DarkFlame",
"EternalNight",
"VoidHunter",
"ShadowCast",
"DimReaper",
"BlackIce",
"NightFury",
"DarkPulse",
"Netherworld",
"GhostFace",
"DarkSide",
"VeilStrike",
"ShadowFire",
"Blackout",
"ChaosVoid",
"DoomBringer",
"GrimDark",
"NightTerror",
"VoidBorn",
"OnyxBlade",
"ShadowHeart",
"DarkTide",
"NullField",
"UmbraSoul",
]),
c4: generateFakeMembers(25, [
"SentinelPrime",
"GuardianAngel",
"Watchman",
"Bulwark",
"Bastion",
"Rampart",
"Phalanx",
"Citadel",
"Fortify",
"Aegis",
"IronGate",
"StoneWall",
"ShieldBearer",
"WardKeeper",
"HoldFast",
"LastStand",
"IronCurtain",
"DefenseGrid",
"Safeguard",
"BunkerDown",
"CastleKeep",
"WallBreaker",
"Garrison",
"TowerShield",
"You",
]),
c5: generateFakeMembers(12, [
"You",
"AlphaWolf",
"BravoSix",
"CharlieHorse",
"DeltaForce",
"EchoStrike",
"FoxtrotDancer",
"GolfSwing",
"HotelCalifornia",
"IndigoChild",
"JulietRose",
"KiloWatt",
]),
c6: [
{
publicId: "p_000000",
displayName: "IronCommander",
role: "leader",
},
{
publicId: "p_000001",
displayName: "You",
role: "officer",
},
{
publicId: "p_000002",
displayName: "SteelTitan",
role: "officer",
},
...[
"HeavyMetal",
"ArmorPlate",
"TankBuster",
"WarMachine",
"BulletProof",
"Juggernaut",
"Ironside",
"ShieldWall",
"FortressX",
"ChromeHeart",
"MetalStorm",
"CopperKing",
"BronzeAge",
"TinSoldier",
"GoldRush",
"SilverLining",
"PlatinumEdge",
].map((name, i) => ({
publicId: `p_${(i + 3).toString(16).padStart(6, "0")}`,
displayName: name,
role: "member" as const,
})),
],
};

const FAKE_JOIN_REQUESTS: Record<string, FakeJoinRequest[]> = {
c6: [
{
publicId: "p_req_010",
displayName: "RocketFuel",
requestedAt: "2026-03-23T10:00:00Z",
},
{
publicId: "p_req_011",
displayName: "LaserBeam",
requestedAt: "2026-03-24T07:45:00Z",
},
{
publicId: "p_req_012",
displayName: "TitanFall",
requestedAt: "2026-03-24T12:30:00Z",
},
],
c5: [
{
publicId: "p_req_001",
displayName: "NeonSamurai",
requestedAt: "2026-03-22T14:30:00Z",
},
{
publicId: "p_req_002",
displayName: "QuantumLeap",
requestedAt: "2026-03-22T18:15:00Z",
},
{
publicId: "p_req_003",
displayName: "ZeroGravity",
requestedAt: "2026-03-23T09:45:00Z",
},
{
publicId: "p_req_004",
displayName: "PixelStorm",
requestedAt: "2026-03-23T11:20:00Z",
},
{
publicId: "p_req_005",
displayName: "TurboCharged",
requestedAt: "2026-03-23T16:00:00Z",
},
{
publicId: "p_req_006",
displayName: "HyperNova",
requestedAt: "2026-03-24T01:10:00Z",
},
{
publicId: "p_req_007",
displayName: "CrypticWolf",
requestedAt: "2026-03-24T08:30:00Z",
},
],
};

const FAKE_CLANS: FakeClan[] = [
{
id: "c1",
name: "Order of the Phoenix",
tag: "OPX",
leaderPublicId: "p_000000",
members: 47,
isOpen: true,
description: "Veteran alliance forged in fire. All skill levels welcome.",
},
{
id: "c2",
name: "Arctic Wolves",
tag: "AWF",
leaderPublicId: "p_000000",
members: 32,
isOpen: true,
description: "Northern hemisphere domination. Active daily players only.",
},
{
id: "c3",
name: "Shadow Empire",
tag: "SHD",
leaderPublicId: "p_000000",
members: 50,
isOpen: false,
description: "Invite only. Top 100 players.",
},
{
id: "c4",
name: "Eternal Guard",
tag: "ETG",
leaderPublicId: "p_000000",
members: 25,
isOpen: false,
description: "Elite defensive strategists. Application required.",
},
{
id: "c5",
name: "Vanguard Protocol",
tag: "VGP",
leaderPublicId: "p_000000",
members: 12,
isOpen: false,
description: "Closed strike team. Recruitment by request only.",
},
{
id: "c6",
name: "Iron Legion",
tag: "IRON",
leaderPublicId: "p_000000",
members: 20,
isOpen: false,
description: "Heavy hitters only. Coordinated warfare division.",
},
];

const FAKE_MY_CLANS: FakeMyClan[] = [
{ clan: FAKE_CLANS[0], role: "leader" },
{ clan: FAKE_CLANS[3], role: "member" },
{ clan: FAKE_CLANS[4], role: "leader" },
{ clan: FAKE_CLANS[5], role: "officer" },
];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Don't ship the clans page against FAKE_* data.

This modal reads from FAKE_CLANS, FAKE_MY_CLANS, FAKE_MEMBERS, and FAKE_JOIN_REQUESTS only, so every user will see invented clans, invented membership, and invented requests. With the new nav and page-clan mount already wired in, this lands as a demo screen instead of a working feature. Please gate the page behind a feature flag or hook it up to real clan data before merge.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/ClanModal.ts` around lines 35 - 387, This modal currently uses
hardcoded test data (FAKE_CLANS, FAKE_MY_CLANS, FAKE_MEMBERS, FAKE_JOIN_REQUESTS
and generateFakeMembers) causing the clans page to show fake content; change
ClanModal so it no longer reads those constants directly: either gate the
component behind a feature flag (check a feature flag/hook before rendering and
return null or a placeholder if disabled) or replace direct usage with real data
hooks/props (e.g., useClans(), useMyClans(), useClanMembers(clanId),
useJoinRequests(clanId) or accept clans/myClans/members/joinRequests as props)
and remove or scope FAKE_* usage to a local demo-only path. Ensure all
references inside ClanModal to FAKE_CLANS, FAKE_MY_CLANS, FAKE_MEMBERS,
FAKE_JOIN_REQUESTS, and generateFakeMembers are updated to use the chosen
real-data hooks/props or are behind the feature-flag gate before merging.

Comment on lines +617 to +637
${role
? html`<span
class="text-[10px] font-bold uppercase tracking-wider px-2 py-0.5 rounded-full
${role === "leader"
? "bg-amber-500/20 text-amber-400 border border-amber-500/30"
: "bg-blue-500/20 text-blue-400 border border-blue-500/30"}"
>
${role}
</span>`
: ""}
${!clan.isOpen
? html`<span
class="text-[10px] font-bold uppercase tracking-wider px-2 py-0.5 rounded-full bg-red-500/20 text-red-400 border border-red-500/30"
>
${translateText("clan_modal.invite_only")}
</span>`
: ""}
</div>
<div class="flex items-center gap-4 mt-1 text-xs text-white/40">
<span>${clan.members} members</span>
<span>${this.resolveLeaderName(clan)}</span>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Some clan labels still bypass i18n.

The role chips and the "members" suffix are rendered as raw English strings instead of translateText(...), so this page will be only partly translated. Please move these labels into resources/lang/en.json and render them through the existing i18n path.

Also applies to: 1031-1040, 1398-1406

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/ClanModal.ts` around lines 617 - 637, The role chips and member
count are hard-coded English; update the UI to use the i18n helper instead: add
keys for role labels (e.g., "clan.role.leader", "clan.role.member", etc.) and
for the members suffix (e.g., "clan.members") in resources/lang/en.json, then
replace raw role text and the "${clan.members} members" rendering with
translateText(...) calls — ensure the role rendering logic in the template that
checks the role variable still uses the same classes and only swaps the
displayed text via translateText(roleKey) and use translateText("clan.members",
{ count: clan.members }) or equivalent pluralization path; keep
resolveLeaderName(clan) unchanged.

Comment on lines +1099 to +1149
(m) => html`
<div
@click=${() => (this.transferTarget = m.publicId)}
class="flex items-center gap-3 py-3 border-b border-white/5 last:border-0 cursor-pointer rounded-lg px-2 transition-all
${this.transferTarget === m.publicId
? "bg-amber-500/10"
: "hover:bg-white/5"}"
>
<div
class="w-8 h-8 rounded-full bg-white/10 flex items-center justify-center text-white/50 text-xs font-bold shrink-0"
>
${m.displayName.charAt(0)}
</div>
<div class="flex-1 min-w-0">
<span
class="text-white text-sm font-medium truncate block"
>${m.displayName}</span
>
<copy-button
compact
.copyText=${m.publicId}
.displayText=${m.publicId}
.showVisibilityToggle=${false}
.showCopyIcon=${false}
></copy-button>
</div>
<span
class="text-[10px] font-bold uppercase tracking-wider px-2 py-0.5 rounded-full shrink-0
${m.role === "officer"
? "bg-purple-500/20 text-purple-400 border border-purple-500/30"
: "bg-white/10 text-white/40 border border-white/10"}"
>
${m.role}
</span>
${this.transferTarget === m.publicId
? html`<svg
xmlns="http://www.w3.org/2000/svg"
class="w-5 h-5 text-amber-400 shrink-0"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M5 13l4 4L19 7"
/>
</svg>`
: ""}
</div>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make leader selection keyboard reachable.

The transfer candidates are clickable div rows with no focus or selected semantics, so a keyboard-only user cannot choose a new leader here. Use a real focusable control for each candidate and expose the selected state.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/ClanModal.ts` around lines 1099 - 1149, The candidate rows
rendered in the (m) => html`...` mapping are non-focusable <div>s and must be
made keyboard reachable and expose selected semantics: replace the clickable
container with a real focusable control (e.g., a <button> or an element with
tabindex="0") that uses the same click handler (set this.transferTarget =
m.publicId), add keyboard activation support (handle Enter/Space to trigger
selection), and expose selection state with aria-selected
(aria-selected={this.transferTarget === m.publicId}) or role="option" inside a
role="listbox" context; also ensure a visible focus style is present when
focused. Use the existing transferTarget identifier and the mapping callback
where m.publicId and m.role are referenced to locate and update the element.

@github-project-automation github-project-automation bot moved this from Triage to Development in OpenFront Release Management Mar 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Development

Development

Successfully merging this pull request may close these issues.

2 participants