Commit Graph

3804 Commits

Author SHA1 Message Date
evanpelle b2f84aad33 delete canvas2D map canvas — WebGL is the only renderer left
After every map-anchored visual moved to WebGL (terrain, territory,
structures, names, selection boxes, ghost preview, move chevrons,
FX) there's nothing drawing to the canvas2D context. Rip it out.
2026-05-16 22:21:17 -07:00
evanpelle 5002dfdc2a delete Pixi build-ghost rendering from StructureIconsLayer
The WebGL ghost (StructurePass + RangeCirclePass + RailroadPass +
CrosshairPass) is fed via GhostPreviewUpdatedEvent and looks correct;
the Pixi-rendered ghost was a duplicate.

Strip the Pixi side out of StructureIconsLayer:
- delete imports for pixi.js / pixi-filters / colord plugins / Theme /
  SpriteFactory / StructureDrawingUtils / bitmapFont / renderNumber
- delete fields: pixicanvas, ghostStage, rootStage, renderer,
  rendererInitialized, theme, factory, filterRedArray, rebuildPending,
  and the Pixi half of ghostUnit (container, priceText, priceBg,
  priceGroup, priceBox, range, rangeLevel, targetingAlly) — now just
  { buildableUnit }
- delete methods: setupRenderer, redraw, rendererOrGLContextLost,
  resizeCanvas, renderLayer, shouldTransform, updateGhostPrice,
  updateGhostRange, plus the Pixi guts of moveGhost /
  createGhostStructure / clearGhostStructure

init() is now sync; the per-RAF state checks move to a tick()-driven
syncGhostState() + renderGhost() (renderGhost still re-queries
buildables, just no longer paints anything).

Net: ~250 LOC gone, no canvas2D drawing left in the layer. The
canvas2D map canvas itself has no remaining writers — ready to be
deleted in a follow-up.
2026-05-16 20:28:59 -07:00
evanpelle 923cba8c2d move multi-unit warship selection box to WebGL SelectionBoxPass
SelectionBoxPass now stores an array of selections and renders one
quad per entry. GPURenderer gains setSelectedUnits(ids) — the
single-unit setSelectedUnit becomes a wrapper. Position + color are
rebuilt each frame from lastUnits; dead unit IDs get pruned in place.

ClientGameRunner's UnitSelectionEvent listener forwards both single
and multi to view.setSelectedUnits — no more single/multi split.

UILayer drops everything canvas2D-related: the offscreen canvas +
context, theme, selectionAnimTime, multiSelectionBoxCenters,
SELECTION_BOX_SIZE, drawSelectionBoxMulti, paintSelectionBoxAt,
clearSelectionBox, paintCell, clearCell, and renderLayer / redraw /
shouldTransform. tick() now only prunes destroyed warships from the
selection list; the layer is purely state + click handling. ~120 LOC
gone.

Tests: UILayer.test.ts updated — drops the canvas/redraw asserts,
adds a multi-selection state assertion.
2026-05-16 20:02:31 -07:00
evanpelle ede0fb7668 move single-unit warship selection box to WebGL SelectionBoxPass
UnitSelectionEvent now forwards to view.setSelectedUnit(unit.id()) in
mountWebGLDebugRenderer; the renderer's SelectionBoxPass draws the
animated stippled outline on the GPU. UILayer still tracks
selectedUnit for game-logic readers (the click handlers) but no longer
paints to canvas2D for it.

Drops drawSelectionBox + lastSelectionBoxCenter (~50 LOC) plus the
per-tick single-unit redraw in tick(). Multi-selection stays on
canvas2D — SelectionBoxPass is single-unit only.

Test update: replaces the now-dead drawSelectionBox spy with a
selectedUnit state assertion + a deselect case.
2026-05-16 19:53:13 -07:00
evanpelle d1651017ea migrate warship drag rectangle from canvas2D to DOM overlay
The shift+drag warship selection rectangle was drawn on a second
offscreen canvas, blitted onto the main canvas2D context every frame
via world-coord transform. It's a screen-space rectangle though, so
none of that math was load-bearing.

Replace with a `<div>` positioned via inline left/top/width/height in
screen pixels. Same color tinting (player territoryColor lightened
0.2, dashed border at 0.85 alpha, fill at 0.06). pointer-events:none
so it doesn't intercept the drag.

Drops ~95 LOC of canvas2D drawing (renderSelectionBox, drawDashedLine,
selectionBoxCanvas/Ctx, the redraw() init, the renderLayer() blit).
One step closer to retiring the canvas2D map canvas — UILayer's
per-unit selection outlines are the last canvas2D draws on it.
2026-05-16 19:37:55 -07:00
evanpelle 45246f2085 make computePlayerStatus live-aware so status icons render
The replay-path computePlayerStatus left alliance/target/embargo/
nukeTargetsMe at false, which meant the WebGL NamePass had no data
for those status icons after we switched names off canvas2D — they
just stopped appearing.

Add an opts param taking localPlayerID + tileState. When localPlayerID
is set, fill the relative flags by checking the local player's
allies/targets/embargoes against each other player's smallID;
embargo is bilateral (either side). nukeTargetsMe walks active nukes
and checks their targetTile's owner via the tileState buffer.

Plumb localPlayerID = myPlayer?.smallID() and tileState from
populateFrame so the live path uses the new mode. Emit an entry when
only a relative flag is true (previously could be dropped if no base
flag was set).

allianceReq and allianceFraction stay deferred (need local PlayerID
string for outgoing requests and current tick for fraction).

18 new tests covering both modes — replay (relative flags forced
false), and live (alliance one-way, target one-way, embargo bilateral,
self-flags suppressed, nukeTargetsMe with/without tileState,
relative-flag-alone emits, localPlayerID=0 falls back to replay,
allianceReq/allianceFraction stay deferred).
2026-05-16 19:21:49 -07:00
evanpelle 3481beba8a delete canvas2D NameLayer; render names via WebGL NamePass
Stop drawing names on canvas2D — NamePass already gets the placement
data (gameView.frameData().names) and lerps positions in-shader. Drop
the runtime passEnabled.name=false override in ClientGameRunner,
remove NameLayer from the layers list, and delete NameLayer.ts.

Known gaps (deferred):
- Player-uploaded flags not in the bundled atlas render as no-flag;
  needs a JIT atlas built at game start.
- The shared computePlayerStatus is the replay variant, so the
  alliance / target / embargo / nukeTargetsMe status icons stay off
  for the local player's perspective. Needs a live-aware variant.
2026-05-16 19:03:38 -07:00
evanpelle 2fec1e994e retire DynamicUILayer, restore warship UX on WebGL
DynamicUILayer was a canvas2D mix of: bonus-event gold/troops popups
(already duplicated by WebGL BonusPopupPass), nuke/transport telegraph
indicators (duplicated by WebGL passes), and a warship move-indicator
chevron drawn via MoveIndicatorUI. Delete the layer outright along
with its three orphan UI helpers (MoveIndicatorUI, NavalTarget,
NukeTelegraph).

That deletion uncovered a pre-existing bug from the "migrate away from
canvas" commit: warship select/move no longer worked. The deleted
UnitLayer had owned the click flow that emits MoveWarshipIntentEvent.
Re-add the flow inside UILayer (which already tracks selected /
multi-selected warships for its selection box): MouseUpEvent →
move-multi → move-single → select-nearest, plus shift+drag box
complete and select-all hotkey.

Wire MoveWarshipIntentEvent → view.showMoveIndicator(tx, ty, ownerID)
in mountWebGLDebugRenderer so the WebGL MoveIndicatorPass draws the
converging-chevron animation at the move target, colored by the
warship's owner. mountWebGLDebugRenderer now takes gameView + eventBus
to resolve the owner and subscribe.
2026-05-16 18:51:34 -07:00
evanpelle 8955be7667 fix: store embargoes as smallID numbers (drop string[] wart)
PlayerState.embargoes was string[] of stringified smallIDs — the
renderer parsed each entry with parseInt() to use as an array index.
Flagged in the integration handoff as something that should be number[].

Switch to number[] end-to-end: renderer type, relation-matrix derive
(no parseInt), PlayerView.setEmbargoSmallIDs / hasEmbargoAgainst
(numeric Array.includes, no String() temporaries), and GameView's
embargo translation pass. Also updates the PlayerView test that pinned
the old format.
2026-05-16 17:45:29 -07:00
evanpelle e87e2cd58c add iconGrowZoom for structures that scale with deep zoom
Previously structures capped at iconScale = 1.0 once zoomed past
iconScaleFactorZoomedOut, staying at a fixed pixel size no matter how
far you zoomed in. They felt overlaid on the map instead of part of it.

Add a third zoom band controlled by structure.iconGrowZoom. Past this
threshold iconScale = uZoom / iconGrowZoom — structures grow with the
canvas (world-anchored, fixed map-area coverage). Plumbed via the
uIconGrowZoom uniform on StructurePass.

Default 7 keeps normal play unchanged; only kicks in at deep zoom.
2026-05-16 17:34:53 -07:00
evanpelle bb619c2c44 add configurable dot size for zoomed-out structures
Structures collapse to a dot when zoomed out past dotsZoomThreshold.
The dot scale was hardcoded to 1.0 / 2.5 (≈0.4) in structure.vert.glsl.

Promote it to a render setting (`structure.dotScale`) so it's tunable
alongside iconSize / dotsZoomThreshold. Default 0.4 preserves current
behavior. Plumbed via uDotScale uniform on StructurePass.
2026-05-16 17:22:45 -07:00
evanpelle 7b8c950bef fix: remove duplicate conquest gold popup on canvas2D
DynamicUILayer subscribed to ConquestEvent and drew a floating "+gold"
text on capture, but the WebGL renderer now draws the same popup via
ConquestPopupPass (fed through applyConquestEvents in uploadFrameData).
The result was two popups stacked on every capture.

Drop the canvas2D handler. Bonus events and unit-death FX use the same
duplication pattern but are left intact for now — separate change when
the WebGL versions are verified visually.
2026-05-16 16:48:06 -07:00
evanpelle 3af1751119 fix: eliminate WebGL camera-sync lag and forced-reflow cost
Two issues with mounting the WebGL renderer alongside canvas2D:

1. One-frame camera lag. WebGL had its own RAF loop independent of
   canvas2D's. When the user panned, WebGL's RAF could fire before
   canvas2D's syncCamera ran, drawing with stale camera state.
   Fix: pass a capturing raf/caf to the renderer so its loop never
   actually schedules itself; invoke the captured frame callback
   synchronously from canvas2D's onPreRender hook, after setCameraState.
   Both renderers now lock-step on a single RAF.

2. Layout thrashing. syncCamera read glCanvas.clientWidth/Height every
   frame, forcing a synchronous layout flush — ~11% CPU under Layout.
   Fix: cache canvas dimensions and update via ResizeObserver. Canvas
   size changes are rare; the cached values are accurate between
   resizes.
2026-05-16 16:42:05 -07:00
evanpelle 5b663fae14 refactor: share renderer state shapes between game and WebGL renderer
PlayerView/UnitView now wrap renderer-shaped state objects (PlayerState,
PlayerStatic, UnitState) directly instead of holding engine wire types.
GameView owns a long-lived FrameData object kept in sync each tick:
players/units/tiles/trail/railroad are mutated in place; derived buffers
(playerStatus, relationMatrix, allianceClusters, nukeTelegraphs,
attackRings) and events are recomputed in a final populateFrame() pass.

The renderer reads gameView.frameData() and the same byte-identical
state objects PlayerView/UnitView wrap. WebGLFrameBuilder shrinks from
~270 to ~70 LOC: palette management + a single uploadFrameData() call,
no per-frame UnitState allocation on the hot path.

Wiring: maxPlayers=1024 on RendererConfig (pre-sizes NamePass/palette/
relation matrix textures); NamePass disabled so HTML NameLayer remains
the only on-screen player names.

Also: 39 new tests covering PlayerView/GameView/FrameData behavior;
replace .data field access in three layer call sites with accessor
methods (betrayals(), type(), getTraitorRemainingTicks()).
2026-05-16 13:27:31 -07:00
evanpelle 53cf2d43f8 migrate away from canvas 2026-05-16 08:55:02 -07:00
evanpelle 9c4ba757c2 add webgl renderer 2026-05-16 08:54:20 -07:00
Mehmet KOZAN b8137927a6 Security: Fix Critical XSS in NewsModal (CVE GHSA-rpr9-rxv7-x643) (#3932)
Subject: Security Vulnerability Report: Critical XSS in OpenFront.io via
sanitize-html (CVE GHSA-rpr9-rxv7-x643)

Hello OpenFront Development Team,

While reviewing the OpenFront.io project, I discovered a critical
Cross-Site Scripting (XSS) vulnerability on the client side. I am
responsibly disclosing this issue to you along with technical details
and a remediation plan so it can be addressed.

Vulnerability Summary
- Vulnerability Type: Cross-Site Scripting (XSS) / Mutation XSS
- Affected Components: src/client/NewsModal.ts,
src/client/components/NewsBox.ts
- Affected Dependency: sanitize-html v2.17.0 (imported via lit-markdown)
- CVE Reference: GHSA-rpr9-rxv7-x643 (CVSS Score: 9.3)

Technical Details
The "News" (Changelog) modal in the game uses the lit-markdown package
to parse markdown content. This package depends on sanitize-html
v2.17.0.

This specific version of sanitize-html has a known parsing flaw when
handling the `<xmp>` tag. When malicious HTML is wrapped inside an
`<xmp>` tag, the sanitization filter misinterprets it and fails to
properly strip the inner HTML. As a result, when the sanitized content
is injected into the DOM, the browser executes the inner HTML.

Proof of Concept (PoC)
If the changelog.md file (or the network response) is manipulated to
include the following payload, the malicious code bypasses sanitization
and executes in the context of the application:

`<xmp><img src=x onerror="alert('System compromised')"></xmp>`

In local testing, injecting this payload directly into the markdown
property of the news-modal component resulted in the `<img>` tag
bypassing the filter and rendering successfully in the DOM.

Impact
This vulnerability introduces a high-risk Stored XSS vector. If an
attacker compromises the server or the CDN hosting the changelog.md
file, or performs a Man-in-the-Middle (MitM) attack:
- Arbitrary JavaScript can be executed in the browsers of all players
who open the News modal.
- Session tokens and authentication data can be stolen.
- Attackers can perform unauthorized actions on behalf of the players
(e.g., disbanding clans or altering settings).

Remediation
The fix is straightforward and requires updating the sanitize-html
library to version 2.17.4 or higher.

You can enforce this update by adding an overrides block to your
package.json:

"overrides": { 
  "sanitize-html": ">=2.17.4" 
}

After updating the package.json, running npm install will apply the
patch.

I am disclosing this vulnerability responsibly and will keep the details
private until a patch has been released. Please let me know if you need
any further information or assistance with the fix.

Best regards,

Mehmet Kozan
Security Researcher
Email: twanske1@gmail.com

---

## Description:

This PR addresses the critical XSS vulnerability detailed above. By
enforcing `sanitize-html` to be version `>=2.17.4` via the `overrides`
block in `package.json`, the `<xmp>` tag parsing flaw is patched. No UI
changes or new text strings were added.

## Please complete the following:

- [ ] I have added screenshots for all UI updates *(N/A - Security patch
in package.json)*
- [ ] I process any text displayed to the user through translateText()
and I've added it to the en.json file *(N/A)*
- [ ] I have added relevant tests to the test directory *(N/A)*
- [x] 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:

hz.mehmetsultan
2026-05-15 16:37:02 -07:00
Aotumuri cd5abd6434 Fix ranked 1v1 requeue opening matchmaking (#3925)
Resolves:
https://discord.com/channels/1284581928254701718/1502285978121801851/1502285978121801851

## Description:

Replace the requeue button-click workaround with a direct
`open-matchmaking` event
Keep consuming only the `requeue` URL parameter while preserving other
query params and hash



https://github.com/user-attachments/assets/7922b4ec-1686-484b-8ce1-b417896ddc44



## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] 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:
aotumuri
2026-05-15 16:34:34 -07:00
Evan ca565eaa1a Subscription upgrade/downgrade + tier management (#3927)
## Summary

- Tier upgrade/downgrade in the Store. The Subscriptions tab now shows
all tiers including the user's current one. Other tiers swap "Subscribe"
→ "Switch" when the user already has a sub, and clicking them calls the
new `POST /subscriptions/@me/change-tier` endpoint with a
direction-aware confirm (upgrade charges prorated diff now, downgrade
gives account credit).
- Owned-tier card renders a **Current Plan** badge in place of the
purchase button. Resolution logic in `resolveCosmetics` now reads
`userMeResponse.player.subscription.tier` (with flare fallback) and
marks that tier as `owned`.
- AccountModal's `<subscription-panel>` reworked into a proper
two-column layout:
- **Left**: tier name, `$X.XX/mo` price, description, daily Pu/Caps
amounts.
- **Right**: status badge (Active / Renews date / Cancels date),
`[Manage] [Change Tier]` button row, `[Cancel]` centered underneath.
When `cancelAtPeriodEnd === true`, the row collapses to a single
`[Reactivate]` button (opens the Stripe portal).
- New `<o-button size="xs">` variant (`py-2 px-3 text-xs`) for the
compact panel buttons.
- Store dollar-purchase price label now supports an optional suffix
(`/mo` for subs only) via a `priceSuffix` prop plumbed through
`CosmeticContainer` → `PurchaseButton`.
- `Api.ts` gains `changeSubscriptionTier(tierName)` with the same
401-handling pattern as the existing subscription helpers.


<img width="1114" height="728" alt="Screenshot 2026-05-14 at 7 09 20 PM"
src="https://github.com/user-attachments/assets/688f83d5-4010-4580-9214-6885af8ec98e"
/>

<img width="1038" height="276" alt="Screenshot 2026-05-14 at 7 09 33 PM"
src="https://github.com/user-attachments/assets/458197f5-a0d4-4c32-bc55-31e5679629b5"
/>

<img width="887" height="286" alt="Screenshot 2026-05-14 at 7 09 55 PM"
src="https://github.com/user-attachments/assets/8149ed82-89cc-4bbe-83de-3614f886b331"
/>

## Discord

evan
2026-05-15 12:01:31 -07:00
RickD004 7dc5d472a7 Change name of map "The Straits" into "Danish Straits" (#3929)
## Description:

Renames TheStraits map. The people that suggested this map told me they
would prefer a more specific name for the map, rather than the generic
one it has right now. So im renaming it into Danish Straits

This map is for v32, it has not been released, it should be fine to
rename


## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] 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:

tri.star1011
2026-05-15 08:59:28 +00:00
RickD004 7359e2bc3b Adds Northwest Passage map (using new additionalNations feature) (#3920)
## Description:

Adds map "Northwest Passage", map of the Canadian Arctic Archipelago ,
Greenland and surroundings. "Northwest Passage" (NWP) is the sea lane
between the Atlantic and Pacific oceans
(https://en.wikipedia.org/wiki/Northwest_Passage) .

21 default nations, based on the towns of the region.

This map uses the brand new additionalNations feature made by FloPinguin
https://github.com/openfrontio/OpenFrontIO/pull/3902 . Adds 39 extra
nations for a total of 60 nations (so that in gamemodes like Humans vs
Nations all the nations have names of real places)

Comparison: 
- Map with default nations
- Map with extra named nations, tested by raising the number of nations
in Solo

<img width="1050" height="412" alt="image"
src="https://github.com/user-attachments/assets/12ed94f1-0615-4fb3-b0d0-dcecb65006ea"
/>
<img width="1089" height="436" alt="image"
src="https://github.com/user-attachments/assets/6e7c11bf-7382-4e36-9433-229a9d463b68"
/>

Terrain source from OpenTopography, already credited in CREDITS.md

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] 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:

tri.star1011
2026-05-14 19:27:46 -07:00
Aotumuri 4250320c9c Fix GitHub translation key category (#3926)
## Description:

The GitHub translation key was incorrectly categorized under news even
though it is used on the main page.
This changes its category to main.

## Please complete the following:

- [ ] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] 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:

aotumuri
2026-05-14 19:27:01 -07:00
Evan bcc453e8cf Add modal URL router (#modal=name&tab=key) (#3924)
## Description

Adds a modal URL router so modals can be opened, deep-linked, and
bookmarked via the hash. URLs of the form `#modal=<name>&tab=<key>&...`
open the named modal and pass remaining keys as args to `onOpen`. The
reverse direction also syncs: opening a modal via the UI updates the
URL, closing it clears the hash, and switching tabs updates `&tab=`.

Builds on the BaseModal refactor from #3923.

### What's new

**`ModalRouter.ts`** — small registry + two-way sync helper.
- `register(name, { tag, pageId? })` declares a modal as router-managed
- `routeFromHash()` parses `#modal=...` and dispatches to
`modal.open(args)`
- `syncOpened/syncClosed/syncTab` push state back into the URL via
`history.replaceState` (no history entries)
- A `routingFromUrl` flag prevents URL→modal→URL feedback loops
- Unknown modal names silently strip the hash

**`BaseModal`** — opt-in URL sync via a `routerName` property.
- When set, BaseModal calls into
`modalRouter.syncOpened/syncClosed/syncTab` from `open` / `close` /
`setActiveTab`
- Modals that own their own URL state (lobby modals) just leave
`routerName` undefined

**`Main.ts`** — registers all routable modals and wires the router.
- `handleUrl()`: adds a `modalRouter.routeFromHash()` branch after the
path-based lobby join
- `onHashUpdate`: when the hash is router-managed, routes via the router
instead of tearing down lobby state

### Routable modals

13 inline modals: store, settings, leaderboard, clan, account, help,
news, language, single-player, ranked, troubleshooting,
territory-patterns, flag-input.

Excluded by design: join-lobby, host-lobby (own URL state via
`/game/<id>`), matchmaking (no URL state).

### Example uses

- Deep link to store flags tab: `/#modal=store&tab=flags`
- Settings keybinds tab: `/#modal=settings&tab=keybinds`
- Cosmetics.ts now redirects to `#modal=store&tab=packs` when a
hard-currency purchase fails for insufficient plutonium (after the
alert), so users can top up directly

### URL behavior

- `replaceState` everywhere — no history entries added when modals open
/ close / switch tabs
- Browser back/forward still works for the existing path-based game flow
- `hashchange` events are router-aware so external hash changes (back
button, manual edit) correctly switch between routed modals without
tearing down lobby state

## Please complete the following:

- [x] I have added screenshots for all UI updates _(no visual changes;
smoke-tested in dev)_
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file _(no new user-visible strings)_
- [ ] I have added relevant tests to the test directory _(no test
coverage; manually tested URL load, UI open, tab switch, close,
hashchange, insufficient-plutonium redirect)_
- [x] 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
2026-05-14 16:49:44 -07:00
Evan bbe727cc84 Refactor modal system: BaseModal renders shell, unified open(args) API (#3923)
## Description

Refactors the modal system so that `BaseModal` owns the `<o-modal>`
shell rendering, tab state, and lifecycle. Modal subclasses now provide
content via small hook methods (`renderHeaderSlot()`, `renderBody(tab)`,
`modalConfig()`) instead of each rebuilding the `<o-modal>` template and
inline-mode branching.

This sets up the foundation for a future modal URL router (e.g.
`#modal=store&tab=flags`), which will be a follow-up PR.

### What changed

**`BaseModal`** — `src/client/components/BaseModal.ts`
- Now renders the `<o-modal>` shell itself; subclasses no longer
duplicate it
- Owns `activeTab` state and dispatches per-tab rendering via
`renderBody(tab)`
- Single `modalConfig()` method returns `{ title?, tabs?, hideHeader?,
hideCloseButton?, alwaysMaximized?, maxWidth? }`
- Uniform `open(args?)` / `close(args?)` interface; subclasses interpret
args in `onOpen(args)` / `onClose(args)`
- Tabbed modals can lazy-load via `onTabEnter(tab)` lifecycle hook
- Re-entrancy guard on `open()` so `showPage()` re-invocations don't
clobber state set by the outer call
- Initial tab defaults to first entry in `modalConfig().tabs` so the
active tab is highlighted on first open

**17 modals migrated** to the new shape:
- Tabbed: Store, UserSetting, Leaderboard, Clan
- Non-tabbed: FlagInput, Account, TokenLogin, News, TerritoryPatterns,
Troubleshooting, SinglePlayer, Matchmaking, RankedModal, Help, Language
- Lobby: JoinLobbyModal, HostLobbyModal (kept their `confirmBeforeClose`
/ `closeAndLeave` / `closeWithoutLeaving` methods)

Per-modal diffs are mostly mechanical:
- Drop the `<o-modal>` wrapper template and the `if (this.inline) return
content` branch
- Drop the inner `<div class="${this.modalContainerClass}">` wrapper
(shell styling now lives on `<o-modal>`)
- Move header content into `renderHeaderSlot()` so it lives in the
sticky header area
- Convert `super.open()`/`super.close()` overrides into
`onOpen(args)`/`onClose(args)` hooks
- For tabbed modals: drop subclass `@state activeTab`, manual
`handleTabChange`, and the `render()` switch — all owned by BaseModal
now

**Other changes:**
- `Store`: in affiliate mode (`#affiliate=X`), tabs are hidden and a
single combined grid of purchasable affiliate items is shown
- `Main.ts`: `joinModal.open(lobbyId, lobbyInfo)` callsites converted to
the new `open({ lobbyId, lobbyInfo })` shape

### Follow-up

Modal URL router (`#modal=X&tab=Y&...`) is a separate PR on top of this
foundation.

## Please complete the following:

- [x] I have added screenshots for all UI updates _(no visual changes;
smoke-tested in dev)_
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file _(no new user-visible strings)_
- [ ] I have added relevant tests to the test directory _(no test
coverage; tested in browser)_
- [x] 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:

evan
2026-05-14 15:33:41 -07:00
Evan e0f73598d6 Add subscriptions: store tab, account panel, manage/cancel (#3918)
## Summary

- Add a **Subscriptions** tab to the Store. Each tier renders as a
`<cosmetic-button>` with description, daily Pu/Caps amounts, and a
Stripe checkout button driven by the existing `createCheckoutSession`
flow.
- Show the player's active subscription in the **Account modal** via a
new `<subscription-panel>` Lit component (status badge, period-end /
cancel-at-period-end, daily currency breakdown).
- **Manage** button opens the Stripe billing portal in a new tab (`POST
/subscriptions/@me/portal`).
- **Cancel** button (hidden once `cancelAtPeriodEnd === true`) calls
`POST /subscriptions/@me/cancel` after a `confirm()` prompt, then
invalidates the userMe cache and refetches.
- Block re-purchase: clicking Subscribe when the user already has a
`subscription:*` flare alerts "Already subscribed" before opening
checkout (upgrade/downgrade flows are out of scope for now).
- Schema additions:
- `CosmeticsSchema.subscriptions: Record<string, SubscriptionSchema>`
(optional) in `src/core/CosmeticSchemas.ts`.
- `UserMeResponse.player.subscription: { tier, status, currentPeriodEnd,
cancelAtPeriodEnd } | null` in `src/core/ApiSchemas.ts`.
- Translations: new `store.*` and `account_modal.sub_*` keys in
`resources/lang/en.json` (English only — Crowdin handles the rest).
- 
<img width="942" height="313" alt="Screenshot 2026-05-14 at 1 13 05 PM"
src="https://github.com/user-attachments/assets/3d28df13-9e03-49f0-bee8-a25f9ad0c420"
/>
<img width="545" height="439" alt="Screenshot 2026-05-14 at 1 13 32 PM"
src="https://github.com/user-attachments/assets/b413b275-d6f2-40dc-9230-d68cd11fb07a"
/>

## Discord

evanpelle
2026-05-14 13:47:16 -07:00
RickD004 5e7f1541b9 Fixes and QoL changes for the "Strait of Malacca" map (#3914)
## Description:

Fixes:
 
Various rivers with pixel-gap errors, that made players and ships unable
to boat out of the river into the sea. This error was reported in the
Discord server
 
<img width="876" height="481" alt="image"
src="https://github.com/user-attachments/assets/9afb31f9-f5a9-4792-bd44-3ea18fe21777"
/>


Also changes:

- Better Terrain (old version had no brown terrain and smidges of white
terrain, which made almost all the map practically green terrain). The
coastlines and terrain area remain the exact same ( the small land
change in manifest was because the old map had little random pixel lakes
all around)

- More Nations (NPCs) , more consistent names for them, and an extra
flag (Aceh)

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] 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:

tri.star1011
2026-05-13 23:26:35 +00:00
Vansh 9e39a7f5a1 fix(client): block Safari page-level pinch-zoom (#3901)
iOS Safari has ignored the `user-scalable=no` viewport hint since iOS

10, so two-finger pinch still zooms the whole page and can softlock the

in-game HUD. Intercept WebKit's non-standard `gesturestart`,

`gesturechange` and `gestureend` events at `document` and call

`preventDefault()` so the page stays put. The game's own pinch-to-zoom

on the map canvas is driven by pointer events (InputHandler) and is

unaffected; browsers that do not fire GestureEvent treat the listeners

as a no-op.

Resolves #2330

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
2026-05-12 16:31:17 -07:00
FloPinguin 990eba6134 Improve MapPlaylist 🎲 (#3904)
## Description:

### 1. `SPECIAL_MODIFIER_POOL` rebalanced
Ticket weights adjusted to roughly track the community "favorite
modifier" poll

<img width="486" height="724" alt="Screenshot 2026-05-11 210740"
src="https://github.com/user-attachments/assets/bb1d2461-beb3-41c0-8d7b-b604db5fc033"
/>

- `isRandomSpawn`: 2 to 4
- `goldMultiplier`: 4 to 6
- `isWaterNukes`: 3 to 4
- `startingGold25M`: 1 to 3
- `startingGold5M`: 5 to 4
- `startingGold1M`: 3 to 2

### 2. New `SPECIAL_TEAM_MAPS` config
Replaces the hardcoded per-map branches in `getTeamCount` and
`buildMapsList`. Each entry maps a `GameMapType` to its preferred
`TeamCountConfig`. Shared constants:
- `SPECIAL_TEAM_FORCE_CHANCE = 0.75` (probability of overriding the
random team weights roll)
- `SPECIAL_TEAM_FREQ_MULTIPLIER = 2` (frequency boost in the team
playlist)

Current entries: Baikal (2), FourIslands (4), Luna (2). Behavior
preserved for the existing maps, but adding another special team map is
now a one-line entry.

### 3. New `FULL_LAND_MAPS` config (TheBox, Alps)
- Water nukes forced on 75% of the time in the special rotation
(overrides `WATER_NUKES_BOOSTED_MAPS`, which still applies its 50% boost
to FourIslands, Baikal, Luna, ArchipelagoSea). Because they make a lot
of fun on these two maps.
- The `isPortsDisabled` modifier is excluded unless water nukes is
boosted on, since ports are pointless on full-land maps. Because this
happened:
<img width="516" height="292" alt="image"
src="https://github.com/user-attachments/assets/cd9ce31d-25d0-4b35-a8ba-bb3ec1c02b70"
/>

### 4. Misc
- Renamed `frequency` constant to `FREQUENCY` for consistency with other
module-level constants.

### 5. Exclude `isNukesDisabled` on special team maps in team mode

On `SPECIAL_TEAM_MAPS` (FourIslands, Baikal, Luna) in team mode, the
`isNukesDisabled` modifier is now excluded from the pool. Otherwise an
extreme warship spam will follow.

## Please complete the following:

- [X] I have added screenshots for all UI updates
- [X] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [X] I have added relevant tests to the test directory
- [X] 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:

FloPinguin
2026-05-11 19:27:02 -07:00
babyboucher 5279f9b4ec Add Alliance Extension Handling (#3903)
## Description:

Allows the Alliance hotkey to extend an alliance as expected by
https://discord.com/channels/1284581928254701718/1503351192921571409
 

## Please complete the following:

- [X] I have added screenshots for all UI updates
- [X] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [X] I have added relevant tests to the test directory
- [X] 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:

babyboucher
2026-05-11 19:25:16 -07:00
Evan 275fd0dccc refactor: collapse per-env Configs into ClientEnv + ServerEnv (#3906)
## Description:

This is a refactor to simplify config handling.

Replaces the per-environment DevConfig/PreprodConfig/ProdConfig class
hierarchy with two static classes: ClientEnv (browser main thread, reads
from window.BOOTSTRAP_CONFIG) and ServerEnv (Node server, reads from
process.env). The four config classes are deleted, the abstract
DefaultServerConfig is gone, and DefaultConfig is renamed to Config.

The values that flow server → client (gameEnv, numWorkers,
turnstileSiteKey, jwtAudience, instanceId) used to be baked into the
hardcoded per-env classes. They're now real env vars on the server,
embedded into a single window.BOOTSTRAP_CONFIG object in index.html at
request time (alongside the existing gitCommit/assetManifest/cdnBase
globals, which moved into the same object), and read back by ClientEnv
on the client. The dev defaults previously hidden inside DevServerConfig
are now explicit in start:server-dev (NUM_WORKERS=2,
TURNSTILE_SITE_KEY=1x..., JWT_AUDIENCE=localhost, etc.) and in
vite.config.ts's html plugin inject.data. Production deploys plumb
NUM_WORKERS and TURNSTILE_SITE_KEY through deploy.yml (GitHub vars) into
the remote env file; JWT_AUDIENCE is derived from DOMAIN in deploy.sh.
The dynamic /api/instance endpoint is gone — INSTANCE_ID rides along in
BOOTSTRAP_CONFIG now.

ServerEnv is the only thing server code touches; ClientEnv is
browser-only. The two classes have intentional overlap (env, numWorkers,
jwtIssuer, gameCreationRate, workerIndex, etc.) since they derive
identical logic from different sources — there's a TODO in each to
consolidate via a shared helper later. The game-logic Config no longer
stores a ServerConfig/ClientEnv reference and its serverConfig() getter
is gone; the one caller (MultiTabModal) now reads ClientEnv.env()
directly. Worker init no longer carries server-config values since
nothing in the worker actually reads them.

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] 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:

evan
2026-05-11 19:24:01 -07:00
evanpelle a597262af9 Merge branch 'v31' 2026-05-11 17:07:12 -07:00
RickD004 834a9757d7 Adds map "The Straits" (#3896)
## Description:

**Adds "The Straits" map:**

A map located around Denmark and the many surrounding straits: Kattegat,
Skagerrak and the Danish straits (thus the name, meant to be a creative
name like "Between Two Seas" and "Gateway to the Atlantic"). This map is
themed in the early 1900s, the nations/NPCs are traditional and
historical regions of Sweden-Norway, Denmark and the Germany.

Relatively small map with ~700k land tiles, similar to World

Inspired by this Discord thread with nearly 20 upvotes:
https://canary.discord.com/channels/1284581928254701718/1482089104110911634/1482089104110911634

<img width="365" height="506" alt="image"
src="https://github.com/user-attachments/assets/5ee16218-34c0-4b8b-9f9b-d33f219760b0"
/>

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] 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:

tri.star1011
2026-05-11 21:35:41 +00:00
babyboucher d6918c77ea Replace hardcoded defaults (#3885)
## Description:

Replaced hard coded defaults with defaultKeybinds

## Please complete the following:

- [X] I have added screenshots for all UI updates
- [X] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [X] I have added relevant tests to the test directory
- [X] 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:

babyboucher
2026-05-11 13:06:59 -07:00
Berk 2b6ebbfe2d fix: add readyState check before endTurn broadcast (#3879)
## Description:

Guard `ws.send()` in `endTurn()` with a `readyState === OPEN` check to
prevent sending messages to WebSocket connections that have already
closed.

Without this guard, broadcasting to a client whose connection closed
between ticks can throw an exception and crash the game loop.

## Please complete the following:

- [X] I have added screenshots for all UI updates
- [X] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [X] I have added relevant tests to the test directory
- [X] 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:

barfires
2026-05-11 13:04:36 -07:00
FrederikJA 89d330cf64 Make setting for go to player on spawn (default on) (#3874)
If this PR fixes an issue, link it below. If not, delete these two
lines.
Resolves #3847 

## Description:
Makes a setting to turn off go to player on spawn.

Most players i have played with are really annoyed about this feature. I
understand that some people like it, which is why i have made it default
on. But i feel like we should be able to turn it off.

## Please complete the following:

- [ ] I have added screenshots for all UI updates
- [X] 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:

FrederikJA
2026-05-11 13:04:00 -07:00
FloPinguin 19280c0b37 Allow mappers to define additionalNations 🗺️ (#3902)
## Description:

Adds an optional `additionalNations` array to map manifests (info.json /
manifest.json), used as a name pool when a game requests more nations
than the map defines (HvN, private lobbies, solo games).

Suggested by mapmaker PatrickPlaysBadly.

When the requested nation count exceeds `nations.length`:
1. The deficit is filled by random picks from `additionalNations`
(collisions with manifest names are skipped).
2. If `additionalNations` still does not cover the deficit, the
remainder is generated procedurally as before.

Each entry supports `name`, optional `flag` and optional `coordinates`.
If `coordinates` are provided, the picked nation gets a spawn cell
(otherwise it spawns like the procedurally generated ones, with no fixed
location).

`Nation.flag` is also relaxed to optional, since many existing manifest
entries already omit it.

## Please complete the following:

- [X] I have added screenshots for all UI updates
- [X] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [X] I have added relevant tests to the test directory
- [X] 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:

FloPinguin
2026-05-11 12:58:34 -07:00
Aotumuri f1d162825e feat: remove spawn timer on singleplayer (#3199)
Resolves #1041 

## Description:

Remove the singleplayer spawn countdown so the game starts when the
player spawns, spawn nations immediately after player spawn, and align
game timer/max-timer timing with the new start point.

Added a singleplayer regression test for spawn-immunity timing
(GameImpl.test.ts) and updated spawn-phase loop tests to use gameType:
GameType.Public where singleplayer behavior is not under test (e.g.
MIRV/AI/Spawn/WinCheck-related suites), eliminating inSpawnPhase()
timeout hangs after the new singleplayer start logic.


https://github.com/user-attachments/assets/c07a585f-1153-490e-88ca-a91fc7ae5756

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] 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:
aotumuri
2026-05-11 12:44:44 -07:00
iamlewis 96e8f5cccd Update privacy policy and terms of service (#3897) v0.31.11 2026-05-11 12:58:28 +01:00
RickD004 2b04c568fc Format the map lists in Main and MapPlaylist (#3889)
## Description:

Format the map lists in Main and MapPlaylist by alphabetical order. Many
maps were already standarized like this, but many recent ones werent.
Ideally, future maps should be added this way too.

Also ran npm run format in these 2 files, as they were not formatted
correctly before.

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] 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:

tri.star1011
2026-05-08 19:13:09 -07:00
RickD004 36c8fc0394 Add map of Taiwan Strait (#3878)
## Description:

Map of the Taiwan and the Chinese Mainland. 

Team heavy map like Baikal and Hormuz. Terrain Source from
OpenTopography, already credited

<img width="1800" height="1511" alt="image"
src="https://github.com/user-attachments/assets/45954469-8199-4882-9efe-899c5df87ce4"
/>

I also took the chance to standarize and sort alphabetically the map
lists in main.go and MapPlaylist.ts.

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] 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:

tri.star 1011

NOTE: If the map gets added, please give contributor to
crunchybbbbb_59469 for this map on Discord. Every file was made by him,
his PR just had weird bugs that didnt allow the PR to be review
automatically

Original PR: https://github.com/openfrontio/OpenFrontIO/pull/3853 by
crunchybbb2-hash
2026-05-08 18:08:17 -07:00
Patrick Plays Badly a3f695ee54 Update dyslexdria map (#3886)
## Description:
Small update to Dylexdria.
Iceland (island) moved for better game play.
Edits to nation locations and additional nations added.
Entire map shifted ~120 pixels for balance reasons. 


## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] 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:
PlaysBadly

---------

Co-authored-by: Ricky G.P. <realtacoco@gmail.com>
v0.31.10
2026-05-08 14:48:29 -07:00
Patrick Plays Badly 6f80951a0a Update dyslexdria map (#3886)
## Description:
Small update to Dylexdria.
Iceland (island) moved for better game play.
Edits to nation locations and additional nations added.
Entire map shifted ~120 pixels for balance reasons. 


## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] 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:
PlaysBadly

---------

Co-authored-by: Ricky G.P. <realtacoco@gmail.com>
2026-05-08 14:47:47 -07:00
Berk 79020e3b1e fix: typo 'handline' -> 'handling' in websocket error log (#3880)
## Description:

Fixes a typo in `GameServer.ts` - `handline` should be `handling` in the
websocket error log message.

## Please complete the following:

- [X] I have added screenshots for all UI updates
- [X] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [X] I have added relevant tests to the test directory
- [X] 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:

barfires
2026-05-08 13:20:44 -07:00
Evan 4889cb83a0 ci: fail PRs when generated maps are out of date (#3876)
## Description:

## Summary
- Adds a `gen-maps` job to the CI workflow that runs `npm run gen-maps`
(Go map generator + prettier) and fails the check if the working tree
has any diff afterwards.
- The failure annotation tells the author to run `npm run gen-maps`
locally and commit the result, so stale generated maps can't slip
through review.
- Pulls the Go toolchain version from `map-generator/go.mod` so the CI
version stays in sync with the module automatically.


## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] 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:

evan
2026-05-06 21:59:39 -06:00
evanpelle 4d0324c5cc run formatter 2026-05-06 21:43:03 -06:00
evanpelle 0976d6f0ec regenerate maps 2026-05-06 21:09:42 -06:00
evanpelle e960b5130a run formatter 2026-05-06 21:08:42 -06:00
dependabot[bot] 922e03d5fb Bump ip-address from 10.1.0 to 10.2.0 in the npm_and_yarn group across 1 directory (#3862)
Bumps the npm_and_yarn group with 1 update in the / directory:
[ip-address](https://github.com/beaugunderson/ip-address).

Updates `ip-address` from 10.1.0 to 10.2.0
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/beaugunderson/ip-address/commits">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=ip-address&package-manager=npm_and_yarn&previous-version=10.1.0&new-version=10.2.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/openfrontio/OpenFrontIO/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-06 20:17:09 -06:00
Ryan 7873bdbcb4 clan stats breakdown (#3869)
## Description:

improvements to clan ui. 
<img width="788" height="290" alt="image"
src="https://github.com/user-attachments/assets/736ca147-bff4-44d8-8180-7b80a85556fe"
/>
added "expand all" and new collapsible sections.


<img width="787" height="550" alt="image"
src="https://github.com/user-attachments/assets/deb2f813-854b-46a9-a767-52c4f749f30f"
/>

which changes to collapse all when expanded

also adds more info about team (d,t,q,2,3,4,5,6,7 team)

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] 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:

w.o.n
2026-05-06 16:10:27 -06:00
Ryan 9ab817cc89 [bugfix] fixes border around clans ui (#3873)
## Description:

fixes border around clans ui
<img width="67" height="705" alt="image"
src="https://github.com/user-attachments/assets/5ee35eb5-b406-4403-b9b4-324769faf061"
/>


also fixes weird padding:
<img width="134" height="244" alt="image"
src="https://github.com/user-attachments/assets/32a84074-afa6-4e9a-98f1-e45aabe4aa2a"
/>

what it should be:
<img width="140" height="206" alt="image"
src="https://github.com/user-attachments/assets/b72b480e-c972-4495-b9da-5c3b411bf590"
/>

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] 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:

w.o.n
2026-05-06 16:10:21 -06:00