Commit Graph

34 Commits

Author SHA1 Message Date
Aotumuri b957515b80 fix 2026-05-26 08:42:50 +09:00
Aotumuri 014032baae Merge remote-tracking branch 'upstream/main' into public-achivements-ui
# Conflicts:
#	src/client/AccountModal.ts
2026-05-25 14:41:57 +09:00
Evan fd6cd762e6 feat: friends panel (#3990)
## Description:

# Add Friends tab to Account modal

## Summary

- Adds a "Friends" tab to the Account modal, alongside Account / Stats /
Games.
- New `<friends-list>` Lit component covering the full friend lifecycle:
send request, accept / deny incoming, withdraw outgoing, remove friend,
paginated list with "Load more".
- New `FriendsApi.ts` wrapping `GET/POST/DELETE /friends*` endpoints
with typed error codes (`not_found` / `conflict` / `bad_request` /
`request_failed`).
- Zod schemas for the friend API responses in `core/ApiSchemas.ts`.
- Translations under a new `friends.*` block in `en.json`.

Friends and pending requests are displayed by public ID via
`copy-button`, matching the existing clan convention.


## 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-23 16:16:16 +01:00
Evan 458d04e278 split account modal into multiple tabs (#3986)
## Description:

The account modal was getting too large, too much scrolling.


<img width="1091" height="779" alt="Screenshot 2026-05-22 at 2 23 23 PM"
src="https://github.com/user-attachments/assets/9553c80d-7d17-4cfd-8992-88bb335a972e"
/>
<img width="970" height="781" alt="Screenshot 2026-05-22 at 2 23 33 PM"
src="https://github.com/user-attachments/assets/847e70eb-57b7-440b-adb6-bb7c18ada43c"
/>
<img width="1100" height="718" alt="Screenshot 2026-05-22 at 2 23 42 PM"
src="https://github.com/user-attachments/assets/5b08d44c-743a-4245-86a3-bf62e01008e9"
/>


## 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-22 15:12:01 +01:00
Aotumuri ebccc7c80f Merge branch 'main' into public-achivements-ui 2026-05-20 06:41:26 +09: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
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
Ryan c2567b48c6 reword to Public Player ID (#3797)
updates wording to "Public Player ID"

## Description:

Describe the PR.

## 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-04-29 16:10:29 -06:00
Evan 62299c9714 standardize UI colors to fit brand guidelines (#3754)
## Description:

We have brand colors:

<img width="738" height="900" alt="Screenshot 2026-04-25 at 12 52 29 PM"
src="https://github.com/user-attachments/assets/aac69e87-91f2-4c3f-9f1e-f69f48f5943e"
/>

So update the homepage & in-game UI to use those colors:

<img width="1185" height="946" alt="Screenshot 2026-04-25 at 12 51
06 PM"
src="https://github.com/user-attachments/assets/89a0b12c-2db1-43d4-9500-fcf405c0f7ff"
/>

Also updated buttons to use the o-button element

## 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-04-25 13:53:21 -06:00
Evan 696e727a39 support for purchasing currency packs (#3629)
## Description:

Adds a currency pack system to the store. Players can purchase packs of
in-game currency (Plutonium and Caps) via Stripe checkout.

What's new:

* Pack schema (PackSchema) — new cosmetic type with currency
(hard/soft), amount, and displayName
* "Packs" tab in the Store — renders purchasable currency packs using
existing CosmeticButton infrastructure
* Stripe checkout flow — new createCurrencyPackCheckout API call and
handlePackPurchase handler
* Currency display in Account modal — shows Plutonium and Caps balances
when logged in
I* con components — <plutonium-icon> (animated green glow + rotate) and
<cap-icon> with new SVG assets
* Currency in UserMeResponse — player.currency.hard /
player.currency.soft added to the API schema

## 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-04-10 15:07:47 -07:00
Evan d5a2cc0fca cosmetic refactor (#3628)
## Description:

The motivation is to have a single "cosmetic-button" element, so we can
abstract out the cosmetic types. This will make it much easier to add
new cosmetic types in the future.

Unifies PatternButton and FlagButton into a single CosmeticButton
component. Extracts a resolveCosmetics() function that flattens patterns
× color palettes + flags into a ResolvedCosmetic[] with relationship
status pre-computed, replacing duplicated resolution logic across four
callers.

* New CosmeticButton — renders patterns or flags based on
ResolvedCosmetic.type
* New resolveCosmetics() — centralizes ownership/purchase/blocked
resolution
* Extracted PatternPreview — canvas rendering split into its own module
* Added type: "pattern" | "flag" discriminator to Zod cosmetic schemas
* Deleted FlagButton.ts and PatternButton.ts
* Added 320-line test suite for resolveCosmetics


## 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-04-09 21:07:07 -07:00
Aotumuri a7a5dacf84 Merge upstream/main into public-achivements-ui 2026-04-03 14:42:57 +09:00
Aotumuri a5c2d4f52f test 2026-03-29 09:58:14 +09:00
scamiv 2be858869c Split runtime and game logic env loading (#3505)
## Description:


This refactors client configuration loading to make the environment
split explicit.

The app currently has two different env concerns:
- the browser main thread needs the live runtime env to select API /
Turnstile / JWT settings
- the worker and game-logic path need a build-time env to select game
config behavior

Before this change, both responsibilities were hidden behind the same
loader, which made the intent unclear and caused confusion around the
worker fallback behavior.

This PR separates those paths explicitly:
- main-thread browser code now uses `getRuntimeClientServerConfig()`
- game creation and worker/game-logic code now uses
`getGameLogicConfig()`
- the build-time game-logic env is represented explicitly as
`GameLogicEnv`

## What Changed

- Added `GameLogicEnv` to model the build-time game config choice
explicitly.
- Added `getRuntimeClientServerConfig()` for live runtime browser config
from `window.BOOTSTRAP_CONFIG`.
- Added `getBuildTimeGameLogicEnv()` and
`getServerConfigForGameLogicEnv()` for build-time worker/game-logic
config.
- Renamed game config loading from `getConfig()` to
`getGameLogicConfig()` to reflect what it actually does.
- Updated browser call sites to use the runtime client config loader.
- Updated worker/game creation paths to use the game-logic config
loader.
- Updated config loader tests to cover both paths.

## Behavior

This keeps the current intended behavior, but makes it explicit:

- Runtime client env:
  - comes from `window.BOOTSTRAP_CONFIG`
- controls live browser integration settings such as API origin,
Turnstile, and JWT audience/issuer

- Build-time game-logic env:
  - comes from bundled `process.env.GAME_ENV`
  - maps:
    - `dev` -> dev game config
    - `staging` -> default/prod game config
    - `prod` -> default/prod game config

That means preprod/staging deployments can continue using prod game
logic while still using staging API/auth settings on the main thread.

## Why

The previous setup worked, but the naming and loader boundaries were
misleading:
- the same function was used for both runtime browser config and
worker/game config
- the worker fallback looked like an implementation detail instead of an
intentional architectural split

This change makes that intent visible in code without changing the
desired deployment behavior.



## 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-03-24 10:54:39 -07:00
scamiv 05e2bc9f0a Improve cacheability with content-hashed public assets and a cacheable app shell (#3494)
## Description:

This reworks asset delivery and cacheability across the app and moves
non-bundled public resources onto immutable, content-hashed URLs.

Vite bundle outputs continue to live under `/assets/**` and remain
content-hashed by Vite. Public resources that were previously fetched
from stable paths in `resources/` now go through a custom hashed
namespace under `/_assets/**`, backed by a generated asset manifest that
is available to the server, browser, and worker runtime.

In parallel, the root app shell is now cacheable shared HTML instead of
request-time `no-store` HTML. Dynamic and live routes remain explicitly
uncached.

## Why
- Improve browser and Cloudflare cacheability for static assets.
- Remove query-string and release-version cache busting for
runtime-fetched assets.
- Allow unchanged public assets to keep the same URL across releases.
- Reduce avoidable work on `/` by serving a shared app shell instead of
rendering HTML on every request.
- Make cache behavior explicit instead of relying on mixed framework
defaults and file-extension heuristics.

## What Changed

### 1. Content-hashed public asset pipeline
- Added a build-time public asset manifest and hashing pipeline for
non-Vite resources.
- Production now emits hashed public assets under `/_assets/**`.
- Added runtime manifest loading for Node so server-rendered paths
resolve against built hashed files instead of rebuilding from source at
runtime.
- Emitted the runtime asset manifest as an ESM module for server
consumption.

Result:
- `/assets/**` = Vite-managed hashed bundle outputs
- `/_assets/**` = custom content-hashed public resources

### 2. Runtime asset URL migration
- Added a shared `assetUrl(...)` resolution path.
- Migrated runtime references away from query-string versioning and
stable source paths.
- Updated browser, worker, and server-side rendering paths to resolve
through the asset manifest.
- Moved map manifests, map binaries, thumbnails, sprites, sounds, fonts,
flags, icons, screenshots, and other runtime-fetched resources onto
hashed URLs.

### 3. Map and preview fixes
- Fixed directory and per-file map asset resolution so map manifest and
binary fetches resolve to the correct hashed URLs.
- Updated preview metadata and map thumbnail paths to use the hashed
asset namespace.
- Fixed runtime manifest loading in prod after deployment.

### 4. Explicit cache policies
- Added explicit immutable cache headers for:
  - `/assets/**`
  - `/_assets/**`
  - worker-prefixed equivalents under `/wN/...`
- Added explicit `no-store` headers for live and dynamic APIs.
- Removed the old `/api/env` bootstrap request and baked `gameEnv` into
the HTML bootstrap instead.

### 5. Cacheable root app shell
- Refactored the root HTML path to serve a shared app shell with:
- `Cache-Control: public, max-age=0, s-maxage=300,
stale-while-revalidate=86400`
- `/` and the SPA fallback now serve shared cacheable HTML instead of
request-time `no-store` rendering.
- `/game/:id` remains dynamic and `no-store`, but now reuses the shared
shell before injecting preview tags.

### 6. Matchmaking instance handling
- Because the app shell is now cacheable, `INSTANCE_ID` was removed from
shared HTML.
- Added `/api/instance` as a temporary `no-store` runtime lookup used
only by matchmaking.
- This preserves correctness with the current random-per-boot
`INSTANCE_ID` model while keeping `/` cacheable, but it is not the
intended long-term design.

## Behavior Changes

### Asset URL contract
Production URLs for non-Vite public resources now change from stable
paths such as:
- `/maps/...`
- `/images/...`
- `/manifest.json`

to content-hashed paths under:
- `/_assets/...`

Examples:
- `/_assets/maps/<map>/manifest.<hash>.json`
- `/_assets/images/Favicon.<hash>.svg`

### Bootstrap/config
- `/api/env` is removed.
- `gameEnv` is now bootstrapped from HTML.

### HTML caching
- `/` and the SPA fallback are now cacheable shared HTML.
- `/game/:id` remains dynamic.

## Cache Matrix After This Branch
- `/_assets/**`: `public, max-age=31536000, immutable`
- `/assets/**`: `public, max-age=31536000, immutable`
- live `/api/**`: explicit `no-store`
- `/api/health`: explicit `no-store`
- `/api/instance`: explicit `no-store`
- `/game/:id`: explicit `no-store`
- `/` and SPA fallback: `public, max-age=0, s-maxage=300,
stale-while-revalidate=86400`

## Notes / Tradeoffs
- `/api/instance` is a temporary compromise. It exists because
`INSTANCE_ID` is currently random per boot, which is not safe to embed
into cacheable shared HTML.
- The current matchmaking flow still asks the client to provide
`instance_id` during `matchmaking/join`. That is functional, but it is
the wrong ownership boundary: instance selection should be handled by
the matchmaking service, not by the browser.
- The cleaner end-state would be:
- make `matchmaking/join` stop requiring `instance_id` from the client,
and let the matchmaking service select a healthy instance from worker
check-ins
- This branch makes the origin behavior edge-cache-friendly, but
Cloudflare still needs matching cache rules if HTML itself should be
cached at the edge.

## Validation
Verified during development with:
- `npx tsc --noEmit`
- `node node_modules\\vite\\bin\\vite.js build`
- `node node_modules\\vitest\\vitest.mjs run
tests/server/RenderHtml.test.ts tests/server/NoStoreHeaders.test.ts
tests/server/StaticAssetCache.test.ts
tests/core/configuration/ConfigLoader.test.ts`

Additional targeted tests added:
- `tests/AssetUrls.test.ts`
- `tests/core/game/FetchGameMapLoader.test.ts`
- `tests/core/configuration/ConfigLoader.test.ts`
- `tests/server/NoStoreHeaders.test.ts`
- `tests/server/StaticAssetCache.test.ts`
- `tests/server/RenderHtml.test.ts`

## Known Existing Warnings
The production build still reports pre-existing warnings that are not
addressed by this branch:
- inconsistent JSON import attributes for `resources/countries.json`
- inconsistent JSON import attributes for `resources/QuickChat.json`
- large chunk warnings from Vite

## Rollout Notes
- Cache rules should treat `/_assets/**` and `/assets/**` as immutable.
- Cloudflare will still classify HTML as dynamic after deploy unless
matching edge cache rules are configured for it.

## Follow-ups
- Remove `/api/instance` by changing `matchmaking/join` so the server
selects the target instance, or by making `INSTANCE_ID` deploy-stable if
the current contract must remain.


## 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-03-23 11:36:52 -07:00
FloPinguin bb1ddfbcaa Make "Not Logged In" button open Account modal & fix Account loading state (#3247)
## Description:

**TerritoryPatternsModal.ts**
- Changed the "Not Logged In" indicator from a static `div` to a
clickable `button`
- Clicking it now closes the skins modal and navigates to the Account
page via `window.showPage("page-account")`
- Added hover effect (`hover:bg-red-500/30`) for visual feedback

**AccountModal.ts**
- Fixed the inline Account modal's loading state ("Fetching account
information...") rendering without a background or header (white text on
light background 💀)
- The loading spinner is now wrapped in `modalContainerClass` (dark
glassmorphic background) with a proper `modalHeader` including the title
and back button, matching the loaded state's appearance

**SinglePlayerModal.ts**
- Changed the "Sign in for achievements" banner from a static `div` to a
clickable `button` that closes the modal and navigates to the Account
page
- Added hover effect for visual feedback

**Matchmaking.ts**
- When the "You must be logged in to play ranked matchmaking" toast
appears, the user is now also navigated to the Account page so they can
log in immediately

## 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-02-19 18:01:27 +00:00
Ryan 70f2abb181 Homepage update & add 3 public lobbies (#3191)
## Description:

Update UI 
check https://homepageupdate.openfront.dev/ 

Improved mobile UI (now fills whole screen for all modals) e.g.:
<img width="432" height="852" alt="image"
src="https://github.com/user-attachments/assets/56de40af-4137-4c57-96b7-3910c9a665b8"
/>

Converted PublicLobby to be "GameModeSelector" to get a nicer 4x4 grid
div, where <GameModeSelector> now handles all the username validation
now (removed redundant code from modals such as matchmaking) also fixed
a bug where someone could have "[XX] X" as thier username (when the
minimum should be 3 chars for their name)

Now visually displays the 3 lobbies ffa/team/special (which is a
continuation from the work done in: #3196 )
<img width="818" height="563" alt="image"
src="https://github.com/user-attachments/assets/a15cd31b-6061-4fb8-83ee-ffde6225cfa7"
/>

updated the background:
<img width="1919" height="807" alt="image"
src="https://github.com/user-attachments/assets/358a7434-51b8-4540-baf2-d1be05053c44"
/>



slightly updated the glassy-look to be less glassy:
<img width="825" height="729" alt="image"
src="https://github.com/user-attachments/assets/1801871b-bbf8-43db-ac53-489337ae80a5"
/>



## 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-02-18 23:11:01 -06:00
Ryan 513be864c9 CopyButton, extract into component (#2934)
## Description:

Extracted the CopyButton into its own component, and now reusing it in
"Account" too.

## 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-01-16 17:08:13 -08:00
Ryan 247c78151c Discord(et al.) embedded URLs (#2740)
## Description:

Changes URL embeds within other platforms, e.g. Discord, WhatsApp & X.

Updates game URLs to `/game/<code>` instead of `/#join=<code>` (required
for embedded URLs). An added benefit of this is that you would be able
to change a url from `openfront.io/game/RQDUy8nP?replay` to
`api.openfront.io/game/RQDUy8nP?replay` (add api. In front) and be in
the right place for the API data.

Updates URLs when joining/leaving private lobbies

Appends a random string to the end of the URL when inside a private
lobby and options change - this is to force discord to update the
embedded details.

Updates URL in different game states to ?lobby / ?live and ?replay.
These do nothing other than being used as a _cache-busting_ solution.


-----------------------------------------------
### **Lobby Info**

Discord:
<img width="556" height="487" alt="image"
src="https://github.com/user-attachments/assets/efd4a06d-506c-4036-9403-ee7c9a669e21"
/>

WhatsApp:
<img width="353" height="339" alt="image"
src="https://github.com/user-attachments/assets/3b2d0c69-988c-424f-9dee-f4e6a6868f6b"
/>


x.com:
<img width="588" height="325" alt="image"
src="https://github.com/user-attachments/assets/d9e78169-20be-4a3e-8df4-8ad41d08a750"
/>


-------------------------
### **Game Win Details**
Discord:
<img width="506" height="468" alt="image"
src="https://github.com/user-attachments/assets/69947774-c943-4a50-b470-5634ed3bf3d7"
/>

WhatsApp:
<img width="770" height="132" alt="image"
src="https://github.com/user-attachments/assets/eec28bf8-bf64-4ab8-954e-03dfdd1aae40"
/>

x.com
<img width="584" height="350" alt="image"
src="https://github.com/user-attachments/assets/168063e2-b707-422b-b7a1-0025f3ebeb92"
/>


## 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-01-13 19:48:00 -08:00
Ryan 7353d785fb Created ModalHeader and moved/unified all modal headers (#2882)
If this PR fixes an issue, link it below. If not, delete these two
lines.
Resolves #(issue number)

## Description:

Moved the Modal Headers into its own class

## 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-01-12 21:03:02 -08:00
Ryan 3e661752af UI refinements (#2859)
## Description:

UI Refinements requested by @evanpelle  check https://ui.openfront.dev

## 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-01-11 14:52:03 -08:00
Ryan 5e6c90d9bb Main Menu UI Overhaul (#2829)
## Description:

Overhauls the Main Menu UI, visit https://menu.openfront.dev to see
everything.

## 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-01-09 20:26:34 -08:00
Wraith e79c805804 refactor(ui): migrate tailwindcss v3 to v4 (#2735)
## Description:

migrate tailwindcss v3 to v4

## 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:

wraith4081

---------

Co-authored-by: iamlewis <lewismmmm@gmail.com>
Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com>
2026-01-06 14:21:52 -08:00
evanpelle 435f2c4fa1 Revert "Christmas Themed Homepage (#2608)"
This reverts commit f256f497ce.
2026-01-01 14:04:13 -08:00
Restart2008 f256f497ce Christmas Themed Homepage (#2608)
## Description:
Adds the following:
- Snowflake animation with snowflakes falling from the top to the bottom
of the screen
- Changed homepage color theme from blue and white to Green and Red.
Also changed the openfront logo to a red, green, and white gradient.
- Added a santa hat on the announcements button

## Please complete the following:
- [x] 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

<img width="1616" height="836" alt="Screenshot 2025-12-12 at 3 01 17 PM"
src="https://github.com/user-attachments/assets/82e29db3-3bc0-4392-b5bf-dd57c15784a3"
/>

<img width="1616" height="836" alt="Screenshot 2025-12-12 at 2 58 54 PM"
src="https://github.com/user-attachments/assets/232da646-6923-4966-acba-5240074e7e3f"
/>


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

Restart

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-12-15 20:26:42 -08:00
evanpelle ef0aff12e2 update tranlation key in account_modal 2025-12-11 16:23:59 -08:00
Evan a09f0c67f1 Update auth & login to follow best practices (#2559)
## Description:

The previous login system used long lived jwts which could be stolen by
XSS. The current system uses long lived refresh tokens that are stored
as http-only cookies. Then the client calls /refresh to get a short
lived jwt using the refresh token. The jwt is stored in memory only so
it's discarded on page close. This way a XSS can only steal the
short-lived jwt.

It also updates how accounts work: players get an account automatically
when they join the webpage. They can see their stats even if not logged
in. If a player wants to keep their account, they can tie it to their
Discord or email, allowing them to log in if cookies are lost.

## 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
2025-12-11 11:25:27 -08:00
evanpelle c5b1bca6c6 fix z-index of homepage modals & buttons so modals always appear above the buttons 2025-11-20 19:35:07 -08:00
Aotumuri 19081bf21b Add PlayerInfoModal (#2058)
## Description:

This PR adds the Player Status display to the AccountModal.
<img width="573" height="640" alt="スクリーンショット 2025-10-11 6 44 25"
src="https://github.com/user-attachments/assets/54c36bde-7c9c-4431-8ac9-e7f4089971f4"
/>


(origin pr:https://github.com/openfrontio/OpenFrontIO/pull/1758)

## 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

---------

Co-authored-by: evanpelle <evanpelle@gmail.com>
2025-10-16 20:01:19 -07:00
evanpelle bc5f18dee8 Hide skins & account login if in iframe (#2126)
## Description:

CrazyGames doesn't allow purchase (must be integrated into their sdk),
so disable it on iframe for now.

## 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
2025-10-01 12:05:10 -07:00
evanpelle acd65e3371 bugfix: hide account button and token modal login on game start (#1986)
## Description:

The account button persisted after game starting

## 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
2025-09-01 11:34:42 -07:00
evanpelle 35ad6f3abf create account on purchase (#1966)
## Description:

When purchasing an item, user will be logged in as their email
automatically.

* Users can be logged in either via discord or email (the top right
button has an email or discord icon depending on which is logged in
* Created AccountModal to show current login and has option to log in
via Discord or send recovery email
* Created TokenLoginModal which is triggered during account recovery or
after purchase
* Update DiscordUserSchema to 
* Removed choco pattern key listeners, they were causing NPEs when empty
input was provided on forms

<img width="408" height="479" alt="Screenshot 2025-08-29 at 5 35 31 PM"
src="https://github.com/user-attachments/assets/a2be5556-b534-4279-931b-799d8ece122c"
/>
support email or discord identity
<img width="801" height="351" alt="Screenshot 2025-08-29 at 5 38 59 PM"
src="https://github.com/user-attachments/assets/9d18ef8f-a6f8-4c22-b583-c31d9b176467"
/>
<img width="97" height="83" alt="Screenshot 2025-08-29 at 5 39 51 PM"
src="https://github.com/user-attachments/assets/994d7ade-fa02-4adb-a6f8-e929af4089b2"
/>
<img width="102" height="83" alt="Screenshot 2025-08-29 at 5 40 03 PM"
src="https://github.com/user-attachments/assets/f829dd49-996b-479d-9b75-d81092e31da4"
/>
<img width="59" height="43" alt="Screenshot 2025-08-29 at 5 40 19 PM"
src="https://github.com/user-attachments/assets/aacf39e7-2528-463b-95cb-a58bc8c2194b"
/>


## 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
2025-08-31 19:09:38 -07:00