Skip to content

Merge skin/effects pickers into one lobby Cosmetics modal#4617

Merged
evanpelle merged 1 commit into
mainfrom
cosmetics
Jul 15, 2026
Merged

Merge skin/effects pickers into one lobby Cosmetics modal#4617
evanpelle merged 1 commit into
mainfrom
cosmetics

Conversation

@evanpelle

Copy link
Copy Markdown
Collaborator

What

Replaces the three lobby cosmetic buttons (skin, flag, effects) with two: Flag and Cosmetics. The new Cosmetics button opens a single tabbed modal (#modal=cosmetics) with:

  • Skins — the combined patterns + image-skins grid (from TerritoryPatternsModal)
  • Effects — all effect types via the tabbed effects-grid, same layout as the Store

Changes

  • New CosmeticsModal and CosmeticsInput (lobby button previews the selected skin/pattern, shows a "Cosmetics" label on defaults, hidden on CrazyGames via no-crazygames)
  • Removed TerritoryPatternsModal, EffectsModal, PatternInput, EffectsInput and their wiring in Main.ts / index.html / LangSelector
  • Selecting a skin or pattern no longer closes the modal — the tile highlight moves to the new selection (matches the Effects tab behavior)
  • i18n: added cosmetics.title/button_title/search; removed the keys orphaned by the deleted components

Notes

  • Deep links to the old #modal=territory-patterns and #modal=effects no longer resolve
  • A follow-up PR (crowns cosmetic) stacks on this branch

🤖 Generated with Claude Code

Replace the three lobby buttons (pattern, flag, effects) with two: Flag
and Cosmetics. The new cosmetics-modal has Skins and Effects tabs
(absorbing TerritoryPatternsModal and EffectsModal, which are removed
along with their input buttons).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The client replaces separate territory-pattern and effects controls with a combined cosmetics input and modal. The modal supports skin and effects tabs, updated selection behavior, new routing and event names, refreshed localization keys, and mobile and desktop layout integration.

Changes

Cosmetics selection flow

Layer / File(s) Summary
Cosmetics modal behavior
src/client/CosmeticsModal.ts
Adds skins and effects tabs, renders the corresponding grids, updates selection state, and keeps the modal open after selections.
Input and client integration
src/client/CosmeticsInput.ts, src/client/Main.ts, src/client/components/PlayPage.ts, index.html
Renames the input and modal elements, updates click events and routing, replaces mobile and desktop controls, and updates modal cleanup.
Cosmetics localization updates
resources/lang/en.json, src/client/LangSelector.ts
Adds cosmetics labels and updates translation refresh targets for the new components.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested labels: UI/UX

Suggested reviewers: flopinguin, scottanderson

Poem

Patterns and effects unite,
Cosmetics shine in one new sight.
Tabs unfold and tiles glow,
Selections stay where players go.
✨ A polished modal steals the show.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: combining skin and effects pickers into a single Cosmetics modal.
Description check ✅ Passed The description is directly related and accurately summarizes the modal merge, removed components, and i18n updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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.

@coderabbitai coderabbitai Bot left a comment

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.

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)
src/client/CosmeticsInput.ts (1)

119-132: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Duplicate ID in the document.

Because CosmeticsInput uses createRenderRoot() { return this; }, it renders its content directly into the Light DOM. Since PlayPage.ts places two of these components on the screen (one for mobile, one for desktop), we end up with multiple buttons sharing id="cosmetics-input". Having duplicate IDs is invalid HTML and can cause weird bugs with styling or scripting. Let's just remove the id property from both buttons!

  • src/client/CosmeticsInput.ts#L119-L132: Remove id="cosmetics-input" from the loading state button.
  • src/client/CosmeticsInput.ts#L150-L156: Remove id="cosmetics-input" from the primary preview button.
🤖 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/CosmeticsInput.ts` around lines 119 - 132, Remove the
id="cosmetics-input" attribute from both buttons rendered by CosmeticsInput: the
loading-state button and the primary preview button. Update both affected sites
in src/client/CosmeticsInput.ts (lines 119-132 and 150-156), leaving their other
attributes and rendering behavior unchanged.
🤖 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/Main.ts`:
- Around line 445-451: Update the cosmetics-input click listener to safely
invoke the modal through optional chaining, so the callback does nothing when
cosmeticsModal is unavailable while preserving the existing open behavior when
it exists.

---

Outside diff comments:
In `@src/client/CosmeticsInput.ts`:
- Around line 119-132: Remove the id="cosmetics-input" attribute from both
buttons rendered by CosmeticsInput: the loading-state button and the primary
preview button. Update both affected sites in src/client/CosmeticsInput.ts
(lines 119-132 and 150-156), leaving their other attributes and rendering
behavior unchanged.
🪄 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: b0dd4c6c-41b6-460e-985e-e2d6e456c314

📥 Commits

Reviewing files that changed from the base of the PR and between ef6e8be and df946e0.

📒 Files selected for processing (9)
  • index.html
  • resources/lang/en.json
  • src/client/CosmeticsInput.ts
  • src/client/CosmeticsModal.ts
  • src/client/EffectsInput.ts
  • src/client/EffectsModal.ts
  • src/client/LangSelector.ts
  • src/client/Main.ts
  • src/client/components/PlayPage.ts
💤 Files with no reviewable changes (2)
  • src/client/EffectsModal.ts
  • src/client/EffectsInput.ts

Comment thread src/client/Main.ts
Comment on lines +445 to 451
// Attach listener to any cosmetics-input component
document.querySelectorAll("cosmetics-input").forEach((cosmeticsInput) => {
cosmeticsInput.addEventListener("cosmetics-input-click", () => {
cosmeticsModal.open();
});
});

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add a safe check before calling open.

If for some reason cosmeticsModal is not found, calling .open() will cause a crash. It is best to use optional chaining here, just like how it is safely checked for the flag input modal above!

🛡️ Proposed fix
     // Attach listener to any cosmetics-input component
     document.querySelectorAll("cosmetics-input").forEach((cosmeticsInput) => {
       cosmeticsInput.addEventListener("cosmetics-input-click", () => {
-        cosmeticsModal.open();
+        cosmeticsModal?.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
// Attach listener to any cosmetics-input component
document.querySelectorAll("cosmetics-input").forEach((cosmeticsInput) => {
cosmeticsInput.addEventListener("cosmetics-input-click", () => {
cosmeticsModal.open();
});
});
// Attach listener to any cosmetics-input component
document.querySelectorAll("cosmetics-input").forEach((cosmeticsInput) => {
cosmeticsInput.addEventListener("cosmetics-input-click", () => {
cosmeticsModal?.open();
});
});
🤖 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/Main.ts` around lines 445 - 451, Update the cosmetics-input click
listener to safely invoke the modal through optional chaining, so the callback
does nothing when cosmeticsModal is unavailable while preserving the existing
open behavior when it exists.

@github-project-automation github-project-automation Bot moved this from Triage to Development in OpenFront Release Management Jul 15, 2026
@evanpelle evanpelle added this to the v33 milestone Jul 15, 2026
@evanpelle evanpelle merged commit 7636f47 into main Jul 15, 2026
15 of 18 checks passed
@evanpelle evanpelle deleted the cosmetics branch July 15, 2026 19:39
@github-project-automation github-project-automation Bot moved this from Development to Complete in OpenFront Release Management Jul 15, 2026
evanpelle added a commit that referenced this pull request Jul 15, 2026
## What

Adds **crowns** as a new cosmetic category, wired end-to-end like
flags/skins, plus a Crowns tab in the lobby cosmetics modal (#4617).

Catalog shape in cosmetics.json:

```json
"crowns": {
  "gold_crown": {
    "name": "gold_crown",
    "url": "...",
    "affiliateCode": null,
    "product": null,
    "priceHard": 5,
    "artist": "...",
    "rarity": "common"
  }
}
```

## Changes

- **Core**: `CrownSchema` + optional `crowns` record in
`CosmeticsSchema`; `crownName` in `PlayerCosmeticRefs`; resolved `crown:
{ name, url }` in `PlayerCosmetics`
- **Server**: `isCrownAllowed` in the privilege checker — requires a
`crown:*` or `crown:<name>` flare, resolves the ref to the catalog URL
- **Client**: crown selection persisted under the `crown` localStorage
key (stale/unowned selections self-clear), `crownRelationship` +
resolve/refs wiring, `"crown"` purchase type, Crowns tab in the store,
and a Crowns tab in the cosmetics modal (owned crowns + Default tile;
selecting persists and keeps the modal open)
- **i18n**: `store.crowns`, `store.no_crowns`
- Tests for schema parsing, privilege checks, and cosmetic resolution

## Not in this PR

- In-game rendering of `player.cosmetics.crown` (name pass /
leaderboards / player panel)
- API-side support (`/shop/purchase` accepting `cosmeticType: "crown"`,
granting `crown:<name>` flares, serving `crowns` in cosmetics.json)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Complete

Development

Successfully merging this pull request may close these issues.

1 participant