mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-25 18:14:36 +00:00
82efcecb80ca445fbc9bc675c7313a50aacb411e
4093 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
82efcecb80 |
Add "assign to me" checkbox to issue templates (#4402)
## Summary Adds an **Assignment** section with a clickable markdown task-list checkbox to the user-facing issue templates, so issue creators can opt in to working on the issue themselves: ```markdown - [ ] I'd like to be assigned to this issue and work on it myself ``` GitHub renders `- [ ]` as a real clickable checkbox in the issue. Checking it only signals intent — a maintainer still performs the actual assignment. ## Templates changed - `bug_report.md` - `feature_request.md` - `new-contribution-template-clean.md` - `new-contribution-template-examples.md` Intentionally **excluded** the API/DB template (`database_request.md`) since that area is maintainer-handled, not contributor work. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
3b84a6f569 |
Feat/anonymize names (#4318)
**Add approved & assigned issue number here:** Resolves #4296 ## Description: Adds an "Anonymous players" option to private lobbies (host toggle, off by default). When it is on, the server sends each client anonymized usernames for everyone except themselves. The lobby creator and admins still see real names so they can moderate. Names are hidden on every player-facing surface: the game start message, lobby info, /api/game/:id, and the link preview. It is enforced server-side, so a client extension cannot read real names off the wire. Initially added as part of our overhaul of OpenFront masters, but this feature can very well be useful for content creators, and other tournament hosts. Anonymized names reuse the existing tribe word lists (no emoji), so they pass UsernameSchema, and they are seeded per user, so a player looks different to different users but stays consistent from the lobby into the game. The saved game record keeps real names (anonymization is a per-send transform, gameStartInfo is never mutated), so replays and stats are unaffected. Nothing changes for normal games. New option selection: <img width="990" height="918" alt="image" src="https://github.com/user-attachments/assets/31df0b0b-7757-4b2b-9bff-84310faee8d9" /> The host, when enabling the option, gets a little eye icon next to the players(including himself to enable/disable the anon names for himself, and/or other player) By default(the names everyone will see are random and unique): <img width="979" height="188" alt="image" src="https://github.com/user-attachments/assets/f0caa4a4-9f14-41d3-89c6-9a38e8c2e6f0" /> Toggling the eye ON for yourself (the host, or any given player, will allow them to see the real names of everyone, in the lobby and in game): <img width="969" height="138" alt="image" src="https://github.com/user-attachments/assets/89abf0e0-1433-43ea-9870-49d96ca46d30" /> ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: zixer._ |
||
|
|
67f7d09fe5 |
Add admin bot HTTP API for managing private games (#4388)
## What A trusted, server-side HTTP API so a bot authenticated with a shared secret can **create private games, change their settings, start them, kick players, and pause/resume** — without opening a WebSocket or joining as a player. Two endpoints under `/api/adminbot/`, reaching the owning worker via the existing `/wN/` nginx routing. They reuse the existing Zod schemas and `GameServer` methods, mirroring the WebSocket intent flow rather than inventing a new wire protocol. | Endpoint | Purpose | | --- | --- | | `POST /api/adminbot/create_game` | Create a private game; the worker mints a self-owned id and returns it (body: `GameConfigSchema.partial()`) | | `POST /api/adminbot/game/:id/intent` | Send a lobby-management intent (body: base `IntentSchema`) | ## How it works - **Auth:** `ADMIN_BOT_API_KEY` env var via the `x-admin-bot-key` header (timing-safe compare). The whole API is **disabled — 404 — when the var is unset**, so non-configured environments expose nothing. It's distinct from the per-instance `ADMIN_TOKEN`, which an external bot can't know. - **`GameServer.handleIntent`** is the unified intent dispatch for both the WebSocket `case "intent"` path and the admin-bot HTTP API. An `IntentActor` carries identity + authority (per-connection lobby-creator/role checks for the WS path; admin authority for the bot). It honors `update_game_config`, `toggle_game_start_timer`, `kick_player`, and `toggle_pause` — **on private games only** (`isPublic()` → 403). Gameplay intents and `mark_disconnected` are rejected (400). - **Private games only.** `create_game` rejects any `gameType` other than `Private` (Public *and* Singleplayer → 400); an omitted `gameType` defaults to `Private`. - **The bot is never a player.** It sends no `clientID`; the server stamps a placeholder `ADMIN_BOT_CLIENT_ID = "ADMINBOT"` (collision-proof — contains `I`/`O`, which `generateID()` never emits). A gameplay intent stamped with it would resolve to no player, so puppeteering is structurally impossible on top of the explicit 400. - **Determinism unchanged:** the only intent that reaches the sim is `toggle_pause`, via the same `addIntent` → turn queue → `ServerTurnMessage` path the WS uses. ## Notable details for review - **`hostCheats` is assigned unconditionally — on purpose.** `updateGameConfig` sets `this.gameConfig.hostCheats = gameConfig.hostCheats` unconditionally, unlike its sibling fields (which are guarded on `!== undefined`). The WS host clears cheats by re-sending the *full* config with `hostCheats: undefined`, so here `undefined` must mean "clear", not "leave unchanged". **Caveat for the admin bot**, which is a *partial*-update client: a partial `update_game_config` that omits `hostCheats` will clear it — the bot should send `hostCheats` explicitly (or a full config) when it wants to keep a previously-set value. - **Deploy wiring:** `ADMIN_BOT_API_KEY` is piped through the deploy steps' `env:` in `deploy.yml`/`release.yml` → `deploy.sh` heredoc → container via `update.sh`'s `--env-file`. The remaining manual step is creating the GitHub secret itself. ## Tests 19 new tests: - `GameServer.handleIntent` admin-bot behavior (per-intent, private-only, post-start guards, placeholder clientID, rejected gameplay/`mark_disconnected` intents). - `create_game` gameType guard (Public and Singleplayer both rejected). - `requireAdminBotKey` middleware (404 disabled / 401 missing / 401 wrong / pass). tsc + eslint clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
d01be405c7 |
Cache maxTroops during leaderboard update (#4379)
**Add approved & assigned issue number here:** Resolves #4316 ## Description: At the beginning of the leaderboard update cycle, evaluates `maxTroops` once for each `PlayerView` and uses the cached value for the rest of the `maxTroops` lookups in the function. Measured a reduction in `updateLeaderboard` processing time from 6ms/sec down to 2ms/sec (measured over the first minute of a singleplayer game on world map and default settings). ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: Demonessica |
||
|
|
06cc01668b |
Fix non-structures being deleteable (#4385)
Resolves #4307 ## Description: Adds a check for the "delete" element of the raidial menu, to make sure the unit attemped to be removed is a structure. Previously, non-structure units such as trains could be deleted using the menu. ## Please complete the following: - [ ] I have added screenshots for all UI updates (No UI updates) - [ ] I process any text displayed to the user through translateText() and I've added it to the en.json file (No text added) - [ ] I have added relevant tests to the test directory (Not sure how this works or if it's needed) ## Please put your Discord username so you can be contacted if a bug or regression is found: unne27 --------- Co-authored-by: unne27 <uno.gunnar.johansson@gmail.com> |
||
|
|
c55ea6bb5a |
Mint game ids on the server, randomly route create-game across workers (#4393)
## What Game creation no longer requires the caller to pick the `gameID` or compute its owning worker. The client POSTs to a prefix-less `/api/create_game`; **nginx (prod) and the vite dev proxy randomly route it to a worker**, which **mints an id that hashes back to itself** and returns it along with its `workerIndex`. ## Why it stays correct The minted id still hashes to the creating worker (via the existing `generateGameIdForWorker`), so everything downstream that derives the worker from the gameID — websocket connect, share URL, join flow — keeps working unchanged. The only thing that moved is *who picks the id and worker*. ## Changes - **`src/server/Worker.ts`** — factor create into a shared `createGameForId`; add `POST /api/create_game` (no id) that mints a self-owned id and returns `gameInfo` + `workerIndex`/`workerPath`. The existing `POST /api/create_game/:id` stays. - **`nginx.conf`** — `location = /api/create_game` proxies to a `random` worker upstream. - **`generate-nginx-upstream.sh` + `Dockerfile`** — the entrypoint generates that upstream from `NUM_WORKERS` at container **start** time. `NUM_WORKERS` isn't known at image build time (the image is built once and deployed with different env), so it can't be baked into `nginx.conf` — hence runtime generation of exactly the live worker ports (no dead-server padding). - **`vite.config.ts`** — dev-only middleware forwards `POST /api/create_game` to a random worker. Vite's `http-proxy` can't pick a per-request random target, so this is a small middleware plugin (same pattern as the existing `serveProprietaryDir`), registered before the `/api` proxy. - **`src/client/HostLobbyModal.ts`** — stop generating the id client-side; use the server's. ## Behavior change to note The host's share link used to be copied **instantly** from a client-generated id. Now the id comes from the server, so the copy waits one create round-trip — I moved the URL build/copy into the create `.then` (and kept the failure path that clears the clipboard). Brief empty-link state in the modal until create resolves. ## Verification - tsc + eslint clean; full suite green (1543 tests). - nginx additions validated with `nginx -t` in isolation (the full file references container-only paths like `/etc/nginx/mime.types`); upstream + `proxy_pass` resolve. - `generate-nginx-upstream.sh` tested with `NUM_WORKERS` set and unset (defaults to 1). Not yet exercised live end-to-end (needs a dev-server restart — `vite.config.ts` + `Worker.ts` changes aren't hot-reloaded). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c63bfb6d94 |
Exempt Dependabot PRs from the PR gate (#4395)
## What Adds a trusted-bot exception to the PR gate so Dependabot's PRs are no longer auto-closed. ## Why The PR gate (`scripts/pr-gate/`, run by `.github/workflows/pr-gate.yml`) auto-closes PRs that don't fit the contribution workflow. Dependabot PRs were getting closed because the bot: - has no repo permission, - links no `approved` issue, and - opens dependency bumps that often exceed the 50-line small-fix cap. ## How - `config.ts` — new `TRUSTED_BOT_AUTHORS` constant (currently `["dependabot[bot]"]`), so the allowlist is easy to extend. - `rules.ts` — new `checkTrustedBot()` rule, wired into `evaluate()` right after the maintainer bypass and before the repo-access check. - `tests/PrGateRules.test.ts` — unit tests for the rule plus an `evaluate()`-level test proving a 5000-line Dependabot PR now passes instead of closing. - `README.md` — documented the new rule in the gate-logic ordering. The match is exact, so a lookalike login (e.g. `not-dependabot[bot]`) won't slip through. Add more bots (Renovate, etc.) to `TRUSTED_BOT_AUTHORS` as needed. ## Testing `npx vitest tests/PrGateRules.test.ts --run` → 39 passed. Lint + prettier clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
1cb84a79df |
Fix stale bot re-commenting on case-mismatched labels (#4394)
## Problem The issue-lifecycle stale rule checked labels with case-sensitive `Array.includes()`, so an issue carrying the `Stale` label (created by the `actions/stale` PR bot) was never recognized as stale. `hasStaleLabel` stayed `false` and the bot re-posted the 7-day warning on **every** daily cron run. Example: [#3441](https://github.com/openfrontio/OpenFrontIO/issues/3441) got the same "hasn't had activity in 7 days" comment ~16 days in a row. ## Fix GitHub label names are case-insensitive (you can't have both `Stale` and `stale`), so the gate should be too. Adds a `hasLabel()` helper in `github.ts` and routes all label checks through it (`STALE`, `KEEP_OPEN`, `APPROVED`, `NOT_APPROVED`). Now an issue gets one stale warning when marked, then silence until the 14-day close. ## Note The Dependabot PR-exemption change (`pr-stale.yml`) is being applied separately — the CI token here lacks `workflow` scope to push workflow-file changes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
a852fa22f2 |
Bump the npm_and_yarn group across 1 directory with 2 updates (#4387)
Bumps the npm_and_yarn group with 2 updates in the / directory: [js-yaml](https://github.com/nodeca/js-yaml) and [ws](https://github.com/websockets/ws). Updates `js-yaml` from 4.1.1 to 4.2.0 <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md">js-yaml's changelog</a>.</em></p> <blockquote> <h2>[4.2.0] - 2026-06-01</h2> <h3>Added</h3> <ul> <li>Added <code>docs/safety.md</code> with notes about processing untrusted YAML.</li> <li>Added <code>maxDepth</code> (100) loader option. Not a problem, but gives a better exception instead of RangeError on stack overflow.</li> <li>Added <code>maxMergeSeqLength</code> (20) loader option. Not a problem after <code>merge</code> fix, but an additional restriction for safety.</li> <li>Added sourcemaps to <code>dist/</code> builds.</li> </ul> <h3>Changed</h3> <ul> <li>Stop resolving numbers with underscores as numeric scalars, <a href="https://redirect.github.com/nodeca/js-yaml/issues/627">#627</a>.</li> <li>Switched dev toolchains to Vite / neostandard.</li> <li>Updated demo.</li> <li>Reorganized tests.</li> <li><code>dist/</code> files are no longer kept in the repository.</li> </ul> <h3>Fixed</h3> <ul> <li>Fix parsing of properties on the first implicit block mapping key, <a href="https://redirect.github.com/nodeca/js-yaml/issues/62">#62</a>.</li> <li>Fix trailing whitespace handling when folding flow scalar lines, <a href="https://redirect.github.com/nodeca/js-yaml/issues/307">#307</a>.</li> <li>Reject top-level block scalars without content indentation, <a href="https://redirect.github.com/nodeca/js-yaml/issues/280">#280</a>.</li> <li>Ensure numbers survive round-trip, <a href="https://redirect.github.com/nodeca/js-yaml/issues/737">#737</a>.</li> <li>Fix test coverage for issue <a href="https://redirect.github.com/nodeca/js-yaml/issues/221">#221</a>.</li> <li>Fix flow scalar trailing whitespace folding, <a href="https://redirect.github.com/nodeca/js-yaml/issues/307">#307</a>.</li> <li>Fix digits in YAML named tag handles.</li> </ul> <h3>Security</h3> <ul> <li>Fix potential DoS via quadratic complexity in merge - deduplicate repeated elements (makes sense for malformed files > 10K).</li> </ul> <h2>[3.14.2] - 2025-11-15</h2> <h3>Security</h3> <ul> <li>Backported v4.1.1 fix to v3</li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/nodeca/js-yaml/commit/590dbabadd172b099c07654fab2eabec8c7a07b9"><code>590dbab</code></a> 4.2.0 released</li> <li><a href="https://github.com/nodeca/js-yaml/commit/f944dc5cd132251752499bdb157f33027d362177"><code>f944dc5</code></a> Add package.json funding field</li> <li><a href="https://github.com/nodeca/js-yaml/commit/f6927192223355b64f2a6e19f3071ccc579ee718"><code>f692719</code></a> Changelog update</li> <li><a href="https://github.com/nodeca/js-yaml/commit/9971a068fe7fb67eeedec7cf15fd2aba1b71bd79"><code>9971a06</code></a> Fix digits in YAML named tag handles</li> <li><a href="https://github.com/nodeca/js-yaml/commit/464a5b854b682691d35cc119500f74595d2e513c"><code>464a5b8</code></a> Fix flow scalar trailing whitespace folding, close <a href="https://redirect.github.com/nodeca/js-yaml/issues/307">#307</a></li> <li><a href="https://github.com/nodeca/js-yaml/commit/1fda4f715d368c74e6e65e44d3201946c9577e54"><code>1fda4f7</code></a> Tests for <a href="https://redirect.github.com/nodeca/js-yaml/issues/567">#567</a>, <a href="https://redirect.github.com/nodeca/js-yaml/issues/565">#565</a></li> <li><a href="https://github.com/nodeca/js-yaml/commit/031ad079a65da5018cbe6280f3b9306e39699b5a"><code>031ad07</code></a> Stop resolving numbers with underscores as numeric scalars, <a href="https://redirect.github.com/nodeca/js-yaml/issues/627">#627</a></li> <li><a href="https://github.com/nodeca/js-yaml/commit/e46d223b37110ec2ba5b5b1da672a8f6862832d6"><code>e46d223</code></a> CI config update</li> <li><a href="https://github.com/nodeca/js-yaml/commit/9023feec80d19f4c39e73d1b25fc7644e93bf628"><code>9023fee</code></a> Add lockfile</li> <li><a href="https://github.com/nodeca/js-yaml/commit/990e6f4d1c51d03b53da0dc6df3c7fc0f61ad909"><code>990e6f4</code></a> Docs update</li> <li>Additional commits viewable in <a href="https://github.com/nodeca/js-yaml/compare/4.1.1...4.2.0">compare view</a></li> </ul> </details> <br /> Updates `ws` from 8.20.1 to 8.21.0 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/websockets/ws/releases">ws's releases</a>.</em></p> <blockquote> <h2>8.21.0</h2> <h1>Features</h1> <ul> <li>Introduced the <code>maxBufferedChunks</code> and <code>maxFragments</code> options (2b2abd45).</li> </ul> <h1>Bug fixes</h1> <ul> <li>Fixed a remote memory exhaustion DoS vulnerability (2b2abd45).</li> </ul> <p>A high volume of tiny fragments and data chunks could be sent by a peer, using modest network traffic, to crash a <code>ws</code> server or client due to OOM.</p> <pre lang="js"><code>import { WebSocket, WebSocketServer } from 'ws'; <p>const wss = new WebSocketServer({ port: 0 }, function () { const data = Buffer.alloc(1); const options = { fin: false }; const { port } = wss.address(); const ws = new WebSocket(<code>ws://localhost:${port}</code>);</p> <p>ws.on('open', function () { (function send() { ws.send(data, options, function (err) { if (err) return; send(); }); })(); });</p> <p>ws.on('error', console.error); ws.on('close', function (code, reason) { console.log(<code>client close - code: ${code} reason: ${reason.toString()}</code>); }); });</p> <p>wss.on('connection', function (ws) { ws.on('error', console.error); ws.on('close', function (code, reason) { console.log(<code>server close - code: ${code} reason: ${reason.toString()}</code>); }); }); </code></pre></p> <p>The vulnerability was responsibly disclosed and fixed by <a href="https://github.com/Nadav0077">Nadav Magier</a>.</p> <p>In vulnerable versions, the issue can be mitigated by lowering the value of the <code>maxPayload</code> option if possible.</p> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/websockets/ws/commit/bca91adf15677e47dbe4f959653452727be28b94"><code>bca91ad</code></a> [dist] 8.21.0</li> <li><a href="https://github.com/websockets/ws/commit/2b2abd458a1b647d0b6033bd62a619c36189839a"><code>2b2abd4</code></a> [security] Limit retained message parts</li> <li><a href="https://github.com/websockets/ws/commit/78eabe2a6677b231bf9c82601bde86ff91639490"><code>78eabe2</code></a> [security] Add latest vulnerability to SECURITY.md</li> <li>See full diff in <a href="https://github.com/websockets/ws/compare/8.20.1...8.21.0">compare view</a></li> </ul> </details> <br /> 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> |
||
|
|
b04d963c45 |
Bump the npm_and_yarn group across 3 directories with 6 updates (#4384)
Bumps the npm_and_yarn group with 6 updates in the / directory: | Package | From | To | | --- | --- | --- | | [dompurify](https://github.com/cure53/DOMPurify) | `3.4.2` | `3.4.11` | | [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `8.0.10` | `8.0.16` | | [@opentelemetry/core](https://github.com/open-telemetry/opentelemetry-js) | `2.7.1` | `2.8.0` | | [esbuild](https://github.com/evanw/esbuild) | `0.27.7` | `0.28.1` | | [shell-quote](https://github.com/ljharb/shell-quote) | `1.8.3` | `1.8.4` | | [undici](https://github.com/nodejs/undici) | `7.25.0` | `7.28.0` | Bumps the npm_and_yarn group with 1 update in the /scripts/issue-lifecycle directory: [esbuild](https://github.com/evanw/esbuild). Bumps the npm_and_yarn group with 1 update in the /scripts/pr-gate directory: [esbuild](https://github.com/evanw/esbuild). Updates `dompurify` from 3.4.2 to 3.4.11 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/cure53/DOMPurify/releases">dompurify's releases</a>.</em></p> <blockquote> <h2>DOMPurify 3.4.11</h2> <ul> <li>Fixed an issue with a leaky config for hooks via <code>setConfig</code>, thanks <a href="https://github.com/trace37labs"><code>@trace37labs</code></a></li> <li>Bumped vulnerable development dependencies to arrive at plain 0 with <code>npm audit</code></li> <li>Updated the <code>osv-scanner</code> suppression list as no vulnerable dependencies are left for now</li> <li>Updated up the linting tool-chain and removed now-redundant lint directives</li> <li>Updated the documentation is several spots, README, wiki, etc.</li> <li>Bumped several dependencies where possible</li> </ul> <h2>DOMPurify 3.4.10</h2> <ul> <li>Refactored codebase for clarity: extracted the public type declarations into <code>types.ts</code></li> <li>Decomposed the three largest sanitizer functions into focused helpers</li> <li>Removed duplicated defaults and dead branches, consolidated <code>SAFE_FOR_TEMPLATES</code> scrubbing into single shared path</li> <li>Improved per-node performance by hoisting the mXSS probe regexes and testing <code>textContent</code> before <code>innerHTML</code></li> <li>Added a deterministic micro-benchmark harness (<code>npm run bench</code>) with a <code>--compare</code> mode</li> <li>Reduced CI cost by running the full three-engine browser suite once per PR</li> <li>Refreshed the <code>demos/</code> folder so every demo runs again, and added a SVG-via-<code><img></code> demo</li> <li>Documented the bench and <code>test:happydom</code> scripts in the README</li> <li>Completed the Attack Classes & Bypass History wiki page</li> <li>Bumped several dependencies where possible</li> </ul> <h2>DOMPurify 3.4.9</h2> <ul> <li>Further improved the handling of Trusted Types config options, thanks <a href="https://github.com/offset"><code>@offset</code></a></li> <li>Further improved the handling of <code>IN_PLACE</code> sanitization, thanks <a href="https://github.com/mozfreddyb"><code>@mozfreddyb</code></a></li> <li>Added more test coverage for <code>IN_PLACE</code> and Trusted Types related usage</li> <li>Bumped several dependencies where possible</li> <li>Updated README and wiki with more accurate documentation & attack samples</li> </ul> <h2>DOMPurify 3.4.8</h2> <ul> <li>Cleaned up the repository root, renamed some and removed unneeded files</li> <li>Fixed an issue with handling of Trusted Types policies, thanks <a href="https://github.com/fulstadev"><code>@fulstadev</code></a></li> <li>Fixed the node iterator for better template scrubbing, thanks <a href="https://github.com/IamLeandrooooo"><code>@IamLeandrooooo</code></a></li> <li>Included formerly missing LICENSE-MPL in published npm package, thanks <a href="https://github.com/asamuzaK"><code>@asamuzaK</code></a></li> <li>Bumped several dependencies where possible</li> </ul> <h2>DOMPurify 3.4.7</h2> <ul> <li>Hardened the handling of Shadow Roots when using <code>IN_PLACE</code>, thanks <a href="https://github.com/GameZoneHacker"><code>@GameZoneHacker</code></a></li> <li>Removed a problem leading to permanent hook pollution, thanks <a href="https://github.com/offset"><code>@offset</code></a></li> <li>Refactored the test suite and expanded test coverage significantly</li> </ul> <h2>DOMPurify 3.4.6</h2> <ul> <li>Fixed several issues with DOM Clobbering in <code>IN_PLACE</code> mode, thanks <a href="https://github.com/offset"><code>@offset</code></a> & <a href="https://github.com/Bankde"><code>@Bankde</code></a></li> <li>Hardened the checks for cross-realm <code>IN_PLACE</code> and Shadow DOM sanitization, thanks <a href="https://github.com/offset"><code>@offset</code></a> & <a href="https://github.com/Bankde"><code>@Bankde</code></a></li> <li>Added more test coverage for <code>IN_PLACE</code> and general DOM Clobbering attacks</li> <li>Bumped several dependencies where possible</li> </ul> <h2>DOMPurify 3.4.5</h2> <ul> <li>Fixed a bypass caused by the new HTML element <code>selectedcontent</code> added in 3.4.4, thanks <a href="https://github.com/KabirAcharya"><code>@KabirAcharya</code></a></li> </ul> <p><strong>Note that this is a security release for an issue introduced in 3.4.4 and should be upgraded to immediately.</strong></p> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/cure53/DOMPurify/commit/0cae5187403132f96a6d357649e4b15633fc210a"><code>0cae518</code></a> release: 3.4.11 (<a href="https://redirect.github.com/cure53/DOMPurify/issues/1494">#1494</a>)</li> <li><a href="https://github.com/cure53/DOMPurify/commit/6ee5716f8336989753611beeca364957c0eb0c3e"><code>6ee5716</code></a> release: 3.4.10 (<a href="https://redirect.github.com/cure53/DOMPurify/issues/1478">#1478</a>)</li> <li><a href="https://github.com/cure53/DOMPurify/commit/52102472d46035857c52df19e44285f8a1e102fc"><code>5210247</code></a> release: 3.4.9 (<a href="https://redirect.github.com/cure53/DOMPurify/issues/1459">#1459</a>)</li> <li><a href="https://github.com/cure53/DOMPurify/commit/bcdd8285412dc9c4c149652aed2d712e790d6ccf"><code>bcdd828</code></a> release: 3.4.8 (<a href="https://redirect.github.com/cure53/DOMPurify/issues/1439">#1439</a>)</li> <li><a href="https://github.com/cure53/DOMPurify/commit/ca30f070c360df162a3e3848e80e6fd3c9e74bff"><code>ca30f07</code></a> release: 3.4.7 (<a href="https://redirect.github.com/cure53/DOMPurify/issues/1414">#1414</a>)</li> <li><a href="https://github.com/cure53/DOMPurify/commit/bb7739e5bccec7e1ab3dae3f3e42d02db3acaaae"><code>bb7739e</code></a> release: 3.4.6 (<a href="https://redirect.github.com/cure53/DOMPurify/issues/1394">#1394</a>)</li> <li><a href="https://github.com/cure53/DOMPurify/commit/011b0c78f2a0f57ee54f5fcccb697a46ca6e63ea"><code>011b0c7</code></a> release: 3.4.5 (<a href="https://redirect.github.com/cure53/DOMPurify/issues/1382">#1382</a>)</li> <li><a href="https://github.com/cure53/DOMPurify/commit/5817ad969c15e67dfcd6cb37248d6e9c1553e7c3"><code>5817ad9</code></a> release: 3.4.4 (<a href="https://redirect.github.com/cure53/DOMPurify/issues/1374">#1374</a>)</li> <li><a href="https://github.com/cure53/DOMPurify/commit/520edb0371a9638f9b51f1798051299a250c686b"><code>520edb0</code></a> release: 3.4.3 (<a href="https://redirect.github.com/cure53/DOMPurify/issues/1352">#1352</a>)</li> <li>See full diff in <a href="https://github.com/cure53/DOMPurify/compare/3.4.2...3.4.11">compare view</a></li> </ul> </details> <br /> Updates `vite` from 8.0.10 to 8.0.16 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/vitejs/vite/releases">vite's releases</a>.</em></p> <blockquote> <h2>v8.0.16</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/v8.0.16/packages/vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>v8.0.15</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/v8.0.15/packages/vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>v8.0.14</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/v8.0.14/packages/vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>v8.0.13</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/v8.0.13/packages/vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>v8.0.12</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/v8.0.12/packages/vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>v8.0.11</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/v8.0.11/packages/vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md">vite's changelog</a>.</em></p> <blockquote> <h2><!-- raw HTML omitted --><a href="https://github.com/vitejs/vite/compare/v8.0.15...v8.0.16">8.0.16</a> (2026-06-01)<!-- raw HTML omitted --></h2> <h3>Bug Fixes</h3> <ul> <li><strong>deps:</strong> reject UNC paths for launch-editor-middleware (<a href="https://redirect.github.com/vitejs/vite/issues/22571">#22571</a>) (<a href="https://github.com/vitejs/vite/commit/50b951225bbf6151eb84a3ad5a454908ab4a76c9">50b9512</a>)</li> <li>reject windows alternate paths (<a href="https://redirect.github.com/vitejs/vite/issues/22572">#22572</a>) (<a href="https://github.com/vitejs/vite/commit/dc245c71e5007ea4d891a025e2d69ac96c736546">dc245c7</a>)</li> </ul> <h2><!-- raw HTML omitted --><a href="https://github.com/vitejs/vite/compare/v8.0.14...v8.0.15">8.0.15</a> (2026-06-01)<!-- raw HTML omitted --></h2> <h3>Features</h3> <ul> <li>send 408 on request timeout (<a href="https://redirect.github.com/vitejs/vite/issues/22476">#22476</a>) (<a href="https://github.com/vitejs/vite/commit/c85c9eeb9aaf41f477b48b057146887bd5620797">c85c9ee</a>)</li> <li>update rolldown to 1.0.3 (<a href="https://redirect.github.com/vitejs/vite/issues/22538">#22538</a>) (<a href="https://github.com/vitejs/vite/commit/646dbedd2870f8ec48df0321177d8aa64bbd1575">646dbed</a>)</li> </ul> <h3>Bug Fixes</h3> <ul> <li>capitalize error messages and remove spurious space in parse error (<a href="https://redirect.github.com/vitejs/vite/issues/22488">#22488</a>) (<a href="https://github.com/vitejs/vite/commit/85a0eff1c82bbb7c99a0fe8e63704316578a40d3">85a0eff</a>)</li> <li><strong>deps:</strong> update all non-major dependencies (<a href="https://redirect.github.com/vitejs/vite/issues/22511">#22511</a>) (<a href="https://github.com/vitejs/vite/commit/2686d7d0b722402204d3bcc687a87adea1bcf9fa">2686d7d</a>)</li> <li><strong>dev:</strong> fix html-proxy cache key mismatch for /@fs/ HTML paths (<a href="https://redirect.github.com/vitejs/vite/issues/21762">#21762</a>) (<a href="https://github.com/vitejs/vite/commit/47c4213f134f562c41ed7c031e4788510cf7e31e">47c4213</a>)</li> <li><strong>glob:</strong> error on relative glob in virtual module when no files match (<a href="https://redirect.github.com/vitejs/vite/issues/22497">#22497</a>) (<a href="https://github.com/vitejs/vite/commit/5c8e98f8b584ac5d42f0f9b8580c49792213b13c">5c8e98f</a>)</li> <li><strong>optimizer:</strong> close the rolldown bundle when write() rejects (<a href="https://redirect.github.com/vitejs/vite/issues/22528">#22528</a>) (<a href="https://github.com/vitejs/vite/commit/e3cfb9deecff563550fa1b8abd27656b8b292815">e3cfb9d</a>)</li> <li><strong>resolve:</strong> provide onWarn for viteResolvePlugin in JS plugin containers (<a href="https://redirect.github.com/vitejs/vite/issues/22509">#22509</a>) (<a href="https://github.com/vitejs/vite/commit/40985f1c09b7696e594e6c5695fbc315d2da2c83">40985f1</a>)</li> </ul> <h3>Miscellaneous Chores</h3> <ul> <li><strong>deps:</strong> update rolldown-related dependencies (<a href="https://redirect.github.com/vitejs/vite/issues/22566">#22566</a>) (<a href="https://github.com/vitejs/vite/commit/3052a67d9350f4c5076ab1c222c4a21a589cbcdd">3052a67</a>)</li> </ul> <h3>Code Refactoring</h3> <ul> <li>correct logic in <code>collectAllModules</code> function (<a href="https://redirect.github.com/vitejs/vite/issues/22562">#22562</a>) (<a href="https://github.com/vitejs/vite/commit/6978a9ceb942c4f5e211d52b8a1e569f8a65c80c">6978a9c</a>)</li> </ul> <h2><!-- raw HTML omitted --><a href="https://github.com/vitejs/vite/compare/v8.0.13...v8.0.14">8.0.14</a> (2026-05-21)<!-- raw HTML omitted --></h2> <h3>Features</h3> <ul> <li>update rolldown to 1.0.2 (<a href="https://redirect.github.com/vitejs/vite/issues/22484">#22484</a>) (<a href="https://github.com/vitejs/vite/commit/96efc88570b6a6ddf1a910f106920cbac07b3cf0">96efc88</a>)</li> </ul> <h3>Bug Fixes</h3> <ul> <li><strong>deps:</strong> update all non-major dependencies (<a href="https://redirect.github.com/vitejs/vite/issues/22471">#22471</a>) (<a href="https://github.com/vitejs/vite/commit/98b81632139d51820f82036e58d6fbbf122b77b3">98b8163</a>)</li> <li><strong>dev:</strong> handle errors when sending messages to vite server (<a href="https://redirect.github.com/vitejs/vite/issues/22450">#22450</a>) (<a href="https://github.com/vitejs/vite/commit/e8e9a34dcf2540139de558a10187630884d10217">e8e9a34</a>)</li> <li><strong>html:</strong> handle trailing slash paths in transformIndexHtml (<a href="https://redirect.github.com/vitejs/vite/issues/22480">#22480</a>) (<a href="https://github.com/vitejs/vite/commit/5d94d1bffdb2a15de9341194d89baec86ce1f693">5d94d1b</a>)</li> <li><strong>optimizer:</strong> pass oxc jsx options to transformSync in dependency scan (<a href="https://redirect.github.com/vitejs/vite/issues/22342">#22342</a>) (<a href="https://github.com/vitejs/vite/commit/b3132dacea9c6e0cf526cd9f0f09d850f577c262">b3132da</a>)</li> </ul> <h3>Miscellaneous Chores</h3> <ul> <li><strong>deps:</strong> update rolldown-related dependencies (<a href="https://redirect.github.com/vitejs/vite/issues/22470">#22470</a>) (<a href="https://github.com/vitejs/vite/commit/7cb728eb629cc677661f1bc52a044ffc0b87fc7f">7cb728e</a>)</li> <li>remove irrelevant commits from changelog (<a href="https://github.com/vitejs/vite/commit/2c69495f250edf01132d4a20128de19dbe836086">2c69495</a>)</li> </ul> <h3>Code Refactoring</h3> <ul> <li><strong>glob:</strong> do not rewrite import path for absolute base (<a href="https://redirect.github.com/vitejs/vite/issues/22310">#22310</a>) (<a href="https://github.com/vitejs/vite/commit/0ae2844ab6d6d1ccf78a2975b8132769fc35b302">0ae2844</a>)</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/vitejs/vite/commit/f94df87ff03b40b65e29bacdc04cc18c7bccaa4a"><code>f94df87</code></a> release: v8.0.16</li> <li><a href="https://github.com/vitejs/vite/commit/dc245c71e5007ea4d891a025e2d69ac96c736546"><code>dc245c7</code></a> fix: reject windows alternate paths (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/22572">#22572</a>)</li> <li><a href="https://github.com/vitejs/vite/commit/50b951225bbf6151eb84a3ad5a454908ab4a76c9"><code>50b9512</code></a> fix(deps): reject UNC paths for launch-editor-middleware (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/22571">#22571</a>)</li> <li><a href="https://github.com/vitejs/vite/commit/8d1b0195fd186d0b3297d7cd17acff6c96797420"><code>8d1b019</code></a> release: v8.0.15</li> <li><a href="https://github.com/vitejs/vite/commit/2686d7d0b722402204d3bcc687a87adea1bcf9fa"><code>2686d7d</code></a> fix(deps): update all non-major dependencies (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/22511">#22511</a>)</li> <li><a href="https://github.com/vitejs/vite/commit/3052a67d9350f4c5076ab1c222c4a21a589cbcdd"><code>3052a67</code></a> chore(deps): update rolldown-related dependencies (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/22566">#22566</a>)</li> <li><a href="https://github.com/vitejs/vite/commit/e3cfb9deecff563550fa1b8abd27656b8b292815"><code>e3cfb9d</code></a> fix(optimizer): close the rolldown bundle when write() rejects (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/22528">#22528</a>)</li> <li><a href="https://github.com/vitejs/vite/commit/6978a9ceb942c4f5e211d52b8a1e569f8a65c80c"><code>6978a9c</code></a> refactor: correct logic in <code>collectAllModules</code> function (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/22562">#22562</a>)</li> <li><a href="https://github.com/vitejs/vite/commit/646dbedd2870f8ec48df0321177d8aa64bbd1575"><code>646dbed</code></a> feat: update rolldown to 1.0.3 (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/22538">#22538</a>)</li> <li><a href="https://github.com/vitejs/vite/commit/85a0eff1c82bbb7c99a0fe8e63704316578a40d3"><code>85a0eff</code></a> fix: capitalize error messages and remove spurious space in parse error (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/22488">#22488</a>)</li> <li>Additional commits viewable in <a href="https://github.com/vitejs/vite/commits/v8.0.16/packages/vite">compare view</a></li> </ul> </details> <br /> Updates `@opentelemetry/core` from 2.7.1 to 2.8.0 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/open-telemetry/opentelemetry-js/releases">@opentelemetry/core's releases</a>.</em></p> <blockquote> <h2>v2.8.0</h2> <h2>2.8.0</h2> <h3>🚀 Features</h3> <ul> <li>feat(sdk-trace-base): pretty-print <code>SpanImpl</code>, <code>Tracer</code>, and <code>BasicTracerProvider</code> via <code>util.inspect</code> so they render through <code>diag</code> and <code>console.log</code> <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/6690">#6690</a> <a href="https://github.com/mcollina"><code>@mcollina</code></a></li> <li>feat(sdk-metrics): implement metric reader self-observability metrics <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/6449">#6449</a> <a href="https://github.com/anuraaga"><code>@anuraaga</code></a></li> <li>feat(core): add <code>hrTimeToSeconds</code> <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/6449">#6449</a> <a href="https://github.com/anuraaga"><code>@anuraaga</code></a></li> </ul> <h3>🐛 Bug Fixes</h3> <ul> <li>fix(core): limit processing of incoming "baggage" header to 8192 bytes <a href="https://github.com/pichlermarc"><code>@pichlermarc</code></a></li> </ul> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/open-telemetry/opentelemetry-js/blob/main/CHANGELOG.md">@opentelemetry/core's changelog</a>.</em></p> <blockquote> <h2>2.8.0</h2> <h3>🚀 Features</h3> <ul> <li>feat(sdk-trace-base): pretty-print <code>SpanImpl</code>, <code>Tracer</code>, and <code>BasicTracerProvider</code> via <code>util.inspect</code> so they render through <code>diag</code> and <code>console.log</code> <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/6690">#6690</a> <a href="https://github.com/mcollina"><code>@mcollina</code></a></li> <li>feat(sdk-metrics): implement metric reader self-observability metrics <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/6449">#6449</a> <a href="https://github.com/anuraaga"><code>@anuraaga</code></a></li> <li>feat(core): add <code>hrTimeToSeconds</code> <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/6449">#6449</a> <a href="https://github.com/anuraaga"><code>@anuraaga</code></a></li> </ul> <h3>🐛 Bug Fixes</h3> <ul> <li>fix(core): limit processing of incoming "baggage" header to 8192 bytes <a href="https://github.com/pichlermarc"><code>@pichlermarc</code></a></li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/open-telemetry/opentelemetry-js/commit/13a035bc695996cf4aec885fef7b9866f48bc555"><code>13a035b</code></a> chore: prepare next release (<a href="https://redirect.github.com/open-telemetry/opentelemetry-js/issues/6756">#6756</a>)</li> <li><a href="https://github.com/open-telemetry/opentelemetry-js/commit/4b13587d1e08b47baf153e5312ccd08a3240d074"><code>4b13587</code></a> Merge commit from fork</li> <li><a href="https://github.com/open-telemetry/opentelemetry-js/commit/71d195c508320295f1892aaed1ee2f1971ffb470"><code>71d195c</code></a> chore(renovate): set minimumReleaseAge to 3 days (<a href="https://redirect.github.com/open-telemetry/opentelemetry-js/issues/6792">#6792</a>)</li> <li><a href="https://github.com/open-telemetry/opentelemetry-js/commit/555fca6ce94fb8d40a5f869dbd28e43143b4e730"><code>555fca6</code></a> Update renovate.json to use matchManagers (<a href="https://redirect.github.com/open-telemetry/opentelemetry-js/issues/6141">#6141</a>)</li> <li><a href="https://github.com/open-telemetry/opentelemetry-js/commit/b711a81d5262904245d70f1857b6f3bc811b22cd"><code>b711a81</code></a> docs(otlp-exporter-base): add typedoc entry points so public API is indexed a...</li> <li><a href="https://github.com/open-telemetry/opentelemetry-js/commit/da704029ccd291d65402f3d1c469bd3f25aec047"><code>da70402</code></a> fix(ci): supply-chain sec: disable caching in release-related workflow (<a href="https://redirect.github.com/open-telemetry/opentelemetry-js/issues/6790">#6790</a>)</li> <li><a href="https://github.com/open-telemetry/opentelemetry-js/commit/002267b1c639aac1d2f1d6e5c7ac3ed023109ea0"><code>002267b</code></a> chore: complete the move to the smaller SPDX license header (<a href="https://redirect.github.com/open-telemetry/opentelemetry-js/issues/6791">#6791</a>)</li> <li><a href="https://github.com/open-telemetry/opentelemetry-js/commit/056ef9c4e1ddf9306477b7ce26acc7be489f9c6c"><code>056ef9c</code></a> feat(sdk-metrics): implement metric reader metrics (<a href="https://redirect.github.com/open-telemetry/opentelemetry-js/issues/6449">#6449</a>)</li> <li><a href="https://github.com/open-telemetry/opentelemetry-js/commit/3bd69ce18011f9a16a7231489d9c3acc8294e8d9"><code>3bd69ce</code></a> fix(configuration): improve environment variable substitution to handle all t...</li> <li><a href="https://github.com/open-telemetry/opentelemetry-js/commit/bfbda7c2d90e1686f51cd0fc4d02d785ab9a9cc0"><code>bfbda7c</code></a> docs(exporter-trace-otlp-grpc): import CompressionAlgorithm from otlp-exporte...</li> <li>Additional commits viewable in <a href="https://github.com/open-telemetry/opentelemetry-js/compare/v2.7.1...v2.8.0">compare view</a></li> </ul> </details> <br /> Updates `esbuild` from 0.27.7 to 0.28.1 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/evanw/esbuild/releases">esbuild's releases</a>.</em></p> <blockquote> <h2>v0.28.1</h2> <ul> <li> <p>Disallow <code>\</code> in local development server HTTP requests (<a href="https://github.com/evanw/esbuild/security/advisories/GHSA-g7r4-m6w7-qqqr">GHSA-g7r4-m6w7-qqqr</a>)</p> <p>This release fixes a security issue where HTTP requests to esbuild's local development server could traverse outside of the serve directory on Windows using a <code>\</code> backslash character. It happened due to the use of Go's <code>path.Clean()</code> function, which only handles Unix-style <code>/</code> characters. HTTP requests with paths containing <code>\</code> are no longer allowed.</p> <p>Thanks to <a href="https://github.com/dellalibera"><code>@dellalibera</code></a> for reporting this issue.</p> </li> <li> <p>Add integrity checks to the Deno API (<a href="https://github.com/evanw/esbuild/security/advisories/GHSA-gv7w-rqvm-qjhr">GHSA-gv7w-rqvm-qjhr</a>)</p> <p>The previous release of esbuild added integrity checks to esbuild's npm install script. This release also adds integrity checks to esbuild's Deno install script. Now esbuild's Deno API will also fail with an error if the downloaded esbuild binary contains something other than the expected content.</p> <p>Note that esbuild's Deno API installs from <code>registry.npmjs.org</code> by default, but allows the <code>NPM_CONFIG_REGISTRY</code> environment variable to override this with a custom package registry. This change means that the esbuild executable served by <code>NPM_CONFIG_REGISTRY</code> must now match the expected content.</p> <p>Thanks to <a href="https://github.com/sondt99"><code>@sondt99</code></a> for reporting this issue.</p> </li> <li> <p>Avoid inlining <code>using</code> and <code>await using</code> declarations (<a href="https://redirect.github.com/evanw/esbuild/issues/4482">#4482</a>)</p> <p>Previously esbuild's minifier sometimes incorrectly inlined <code>using</code> and <code>await using</code> declarations into subsequent uses of that declaration, which then fails to dispose of the resource correctly. This bug happened because inlining was done for <code>let</code> and <code>const</code> declarations by avoiding doing it for <code>var</code> declarations, which no longer worked when more declaration types were added. Here's an example:</p> <pre lang="js"><code>// Original code { using x = new Resource() x.activate() } <p>// Old output (with --minify)<br /> new Resource().activate();</p> <p>// New output (with --minify)<br /> {using e=new Resource;e.activate()}<br /> </code></pre></p> </li> <li> <p>Fix module evaluation when an error is thrown (<a href="https://redirect.github.com/evanw/esbuild/issues/4461">#4461</a>, <a href="https://redirect.github.com/evanw/esbuild/pull/4467">#4467</a>)</p> <p>If an error is thrown during module evaluation, esbuild previously didn't preserve the state of the module for subsequent module references. This was observable if <code>import()</code> or <code>require()</code> is used to import a module multiple times. The thrown error is supposed to be thrown by every call to <code>import()</code> or <code>require()</code>, not just the first. With this release, esbuild will now throw the same error every time you call <code>import()</code> or <code>require()</code> on a module that throws during its evaluation.</p> </li> <li> <p>Fix some edge cases around the <code>new</code> operator (<a href="https://redirect.github.com/evanw/esbuild/issues/4477">#4477</a>)</p> <p>Previously esbuild incorrectly printed certain edge cases involving complex expressions inside the target of a <code>new</code> expression (specifically an optional chain and/or a tagged template literal). The generated code for the <code>new</code> target was not correctly wrapped with parentheses, and either contained a syntax error or had different semantics. These edge cases have been fixed so that they now correctly wrap the <code>new</code> target in parentheses. Here is an example of some affected code:</p> <pre lang="js"><code>// Original code new (foo()`bar`)() new (foo()?.bar)() <p>// Old output<br /> new foo()<code>bar</code>();<br /> new (foo())?.bar();</p> <p></code></pre></p> </li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/evanw/esbuild/blob/main/CHANGELOG.md">esbuild's changelog</a>.</em></p> <blockquote> <h2>0.28.1</h2> <ul> <li> <p>Disallow <code>\</code> in local development server HTTP requests (<a href="https://github.com/evanw/esbuild/security/advisories/GHSA-g7r4-m6w7-qqqr">GHSA-g7r4-m6w7-qqqr</a>)</p> <p>This release fixes a security issue where HTTP requests to esbuild's local development server could traverse outside of the serve directory on Windows using a <code>\</code> backslash character. It happened due to the use of Go's <code>path.Clean()</code> function, which only handles Unix-style <code>/</code> characters. HTTP requests with paths containing <code>\</code> are no longer allowed.</p> <p>Thanks to <a href="https://github.com/dellalibera"><code>@dellalibera</code></a> for reporting this issue.</p> </li> <li> <p>Add integrity checks to the Deno API (<a href="https://github.com/evanw/esbuild/security/advisories/GHSA-gv7w-rqvm-qjhr">GHSA-gv7w-rqvm-qjhr</a>)</p> <p>The previous release of esbuild added integrity checks to esbuild's npm install script. This release also adds integrity checks to esbuild's Deno install script. Now esbuild's Deno API will also fail with an error if the downloaded esbuild binary contains something other than the expected content.</p> <p>Note that esbuild's Deno API installs from <code>registry.npmjs.org</code> by default, but allows the <code>NPM_CONFIG_REGISTRY</code> environment variable to override this with a custom package registry. This change means that the esbuild executable served by <code>NPM_CONFIG_REGISTRY</code> must now match the expected content.</p> <p>Thanks to <a href="https://github.com/sondt99"><code>@sondt99</code></a> for reporting this issue.</p> </li> <li> <p>Avoid inlining <code>using</code> and <code>await using</code> declarations (<a href="https://redirect.github.com/evanw/esbuild/issues/4482">#4482</a>)</p> <p>Previously esbuild's minifier sometimes incorrectly inlined <code>using</code> and <code>await using</code> declarations into subsequent uses of that declaration, which then fails to dispose of the resource correctly. This bug happened because inlining was done for <code>let</code> and <code>const</code> declarations by avoiding doing it for <code>var</code> declarations, which no longer worked when more declaration types were added. Here's an example:</p> <pre lang="js"><code>// Original code { using x = new Resource() x.activate() } <p>// Old output (with --minify)<br /> new Resource().activate();</p> <p>// New output (with --minify)<br /> {using e=new Resource;e.activate()}<br /> </code></pre></p> </li> <li> <p>Fix module evaluation when an error is thrown (<a href="https://redirect.github.com/evanw/esbuild/issues/4461">#4461</a>, <a href="https://redirect.github.com/evanw/esbuild/pull/4467">#4467</a>)</p> <p>If an error is thrown during module evaluation, esbuild previously didn't preserve the state of the module for subsequent module references. This was observable if <code>import()</code> or <code>require()</code> is used to import a module multiple times. The thrown error is supposed to be thrown by every call to <code>import()</code> or <code>require()</code>, not just the first. With this release, esbuild will now throw the same error every time you call <code>import()</code> or <code>require()</code> on a module that throws during its evaluation.</p> </li> <li> <p>Fix some edge cases around the <code>new</code> operator (<a href="https://redirect.github.com/evanw/esbuild/issues/4477">#4477</a>)</p> <p>Previously esbuild incorrectly printed certain edge cases involving complex expressions inside the target of a <code>new</code> expression (specifically an optional chain and/or a tagged template literal). The generated code for the <code>new</code> target was not correctly wrapped with parentheses, and either contained a syntax error or had different semantics. These edge cases have been fixed so that they now correctly wrap the <code>new</code> target in parentheses. Here is an example of some affected code:</p> <pre lang="js"><code>// Original code new (foo()`bar`)() new (foo()?.bar)() <p>// Old output<br /> new foo()<code>bar</code>();<br /> new (foo())?.bar();<br /> </code></pre></p> </li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/evanw/esbuild/commit/bb9db84c02433fbe37b3509f53f9f3e3cc48725e"><code>bb9db84</code></a> publish 0.28.1 to npm</li> <li><a href="https://github.com/evanw/esbuild/commit/9ff053e53b8eeb990f59355dbea365277ac45ee2"><code>9ff053e</code></a> security: add integrity checks to the Deno API</li> <li><a href="https://github.com/evanw/esbuild/commit/0a9bf2135b67c7e28989a5ba19f0f000805a5ab5"><code>0a9bf21</code></a> enforce non-negative size in gzip parser</li> <li><a href="https://github.com/evanw/esbuild/commit/e2a1a7132058ee067fe736eac15f695861b8654e"><code>e2a1a71</code></a> security: forbid <code>\\</code> in local dev server requests</li> <li><a href="https://github.com/evanw/esbuild/commit/83a2cbfc35809f4fd5152da59572d7bed7739d78"><code>83a2cbf</code></a> fix <a href="https://redirect.github.com/evanw/esbuild/issues/4482">#4482</a>: don't inline <code>using</code> declarations</li> <li><a href="https://github.com/evanw/esbuild/commit/308ad745d824c77bc607603451b257d0f2fd9a38"><code>308ad74</code></a> fix <a href="https://redirect.github.com/evanw/esbuild/issues/4471">#4471</a>: renaming of nested <code>var</code> declarations</li> <li><a href="https://github.com/evanw/esbuild/commit/f013f5f99a015bce92ec48d49181d4ad3177b29b"><code>f013f5f</code></a> fix some typos</li> <li><a href="https://github.com/evanw/esbuild/commit/aafd6e48b1088336a5f5a17e930be7e840d43d8c"><code>aafd6e4</code></a> chore: fix some minor issues in comments (<a href="https://redirect.github.com/evanw/esbuild/issues/4462">#4462</a>)</li> <li><a href="https://github.com/evanw/esbuild/commit/15300c30b5e22f7cfcbed850c246d35095658386"><code>15300c3</code></a> follow up: cjs evaluation fixes</li> <li><a href="https://github.com/evanw/esbuild/commit/1bda0c31d7697c0af44b3ab39b81e599e559a395"><code>1bda0c3</code></a> fix <a href="https://redirect.github.com/evanw/esbuild/issues/4461">#4461</a>, fix <a href="https://redirect.github.com/evanw/esbuild/issues/4467">#4467</a>: esm evaluation fixes</li> <li>Additional commits viewable in <a href="https://github.com/evanw/esbuild/compare/v0.27.7...v0.28.1">compare view</a></li> </ul> </details> <br /> Updates `shell-quote` from 1.8.3 to 1.8.4 <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/ljharb/shell-quote/blob/main/CHANGELOG.md">shell-quote's changelog</a>.</em></p> <blockquote> <h2><a href="https://github.com/ljharb/shell-quote/compare/v1.8.3...v1.8.4">v1.8.4</a> - 2026-05-22</h2> <h3>Commits</h3> <ul> <li>[Fix] <code>quote</code>: validate object-token shapes <a href="https://github.com/ljharb/shell-quote/commit/4378a6e613db5948168684864e49b42b83134d2d"><code>4378a6e</code></a></li> <li>[Dev Deps] update <code>@ljharb/eslint-config</code>, <code>auto-changelog</code>, <code>eslint</code>, <code>npmignore</code> <a href="https://github.com/ljharb/shell-quote/commit/22ebec04349065a45ad8afc8cc8d53c4624634a6"><code>22ebec0</code></a></li> <li>[Tests] increase coverage <a href="https://github.com/ljharb/shell-quote/commit/9f3caa31900cc6ee64858b31134144c648ce206d"><code>9f3caa3</code></a></li> <li>[readme] replace runkit CI badge with shields.io check-runs badge <a href="https://github.com/ljharb/shell-quote/commit/3344a047dd1e95f71c4ca27522cbfd05c56277e0"><code>3344a04</code></a></li> <li>[Dev Deps] update <code>@ljharb/eslint-config</code> <a href="https://github.com/ljharb/shell-quote/commit/699c5113d135f4d4591574bebf173334ffa453d4"><code>699c511</code></a></li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/ljharb/shell-quote/commit/ff166e2b63eb5f932bd131a8886a99e9afdf45ae"><code>ff166e2</code></a> v1.8.4</li> <li><a href="https://github.com/ljharb/shell-quote/commit/4378a6e613db5948168684864e49b42b83134d2d"><code>4378a6e</code></a> [Fix] <code>quote</code>: validate object-token shapes</li> <li><a href="https://github.com/ljharb/shell-quote/commit/22ebec04349065a45ad8afc8cc8d53c4624634a6"><code>22ebec0</code></a> [Dev Deps] update <code>@ljharb/eslint-config</code>, <code>auto-changelog</code>, <code>eslint</code>, `npmig...</li> <li><a href="https://github.com/ljharb/shell-quote/commit/9f3caa31900cc6ee64858b31134144c648ce206d"><code>9f3caa3</code></a> [Tests] increase coverage</li> <li><a href="https://github.com/ljharb/shell-quote/commit/3344a047dd1e95f71c4ca27522cbfd05c56277e0"><code>3344a04</code></a> [readme] replace runkit CI badge with shields.io check-runs badge</li> <li><a href="https://github.com/ljharb/shell-quote/commit/699c5113d135f4d4591574bebf173334ffa453d4"><code>699c511</code></a> [Dev Deps] update <code>@ljharb/eslint-config</code></li> <li>See full diff in <a href="https://github.com/ljharb/shell-quote/compare/v1.8.3...v1.8.4">compare view</a></li> </ul> </details> <br /> Updates `undici` from 7.25.0 to 7.28.0 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/nodejs/undici/releases">undici's releases</a>.</em></p> <blockquote> <h2>v7.28.0</h2> <h1>⚠️ Security Release</h1> <p>This release line addresses <strong>7 security advisories</strong>, all shipped in <strong>v7.28.0</strong>.</p> <blockquote> <p><strong>Action required:</strong> Upgrade to <strong>undici 7.28.0</strong> or later.</p> <pre lang="sh"><code>npm install undici@^7.28.0 </code></pre> </blockquote> <p>The v7 line is <strong>not</strong> affected by GHSA-38rv-x7px-6hhq (CVE-2026-9675), which is an 8.x-only regression.</p> <blockquote> <p><strong>Note on GHSA-hm92-r4w5-c3mj:</strong> this fix shipped in <strong>v7.28.0</strong>, not the earlier 7.2x line — the vulnerable single-pool code was still present through <code>v7.27.2</code>. The per-origin pool fix is <a href="https://github.com/nodejs/undici/commit/3805b8f8"><code>3805b8f8</code></a> (<a href="https://redirect.github.com/nodejs/undici/pull/5041">#5041</a>).</p> </blockquote> <h2>Summary</h2> <table> <thead> <tr> <th>Advisory</th> <th>CVE</th> <th>Severity (CVSS)</th> <th>Fixed in</th> <th>Fix commit</th> </tr> </thead> <tbody> <tr> <td><a href="https://github.com/nodejs/undici/security/advisories/GHSA-vxpw-j846-p89q">GHSA-vxpw-j846-p89q</a></td> <td>CVE-2026-12151</td> <td>High (7.5)</td> <td>7.28.0</td> <td><a href="https://github.com/nodejs/undici/commit/8cb10f98"><code>8cb10f98</code></a></td> </tr> <tr> <td><a href="https://github.com/nodejs/undici/security/advisories/GHSA-vmh5-mc38-953g">GHSA-vmh5-mc38-953g</a></td> <td>CVE-2026-9697</td> <td>High (7.4)</td> <td>7.28.0</td> <td><a href="https://github.com/nodejs/undici/commit/04201f89"><code>04201f89</code></a></td> </tr> <tr> <td><a href="https://github.com/nodejs/undici/security/advisories/GHSA-hm92-r4w5-c3mj">GHSA-hm92-r4w5-c3mj</a></td> <td>CVE-2026-6734</td> <td>High (7.5)</td> <td>7.28.0</td> <td><a href="https://github.com/nodejs/undici/commit/3805b8f8"><code>3805b8f8</code></a></td> </tr> <tr> <td><a href="https://github.com/nodejs/undici/security/advisories/GHSA-pr7r-676h-xcf6">GHSA-pr7r-676h-xcf6</a></td> <td>CVE-2026-9678</td> <td>Moderate (5.9)</td> <td>7.28.0</td> <td><a href="https://github.com/nodejs/undici/commit/85a24055"><code>85a24055</code></a></td> </tr> <tr> <td><a href="https://github.com/nodejs/undici/security/advisories/GHSA-p88m-4jfj-68fv">GHSA-p88m-4jfj-68fv</a></td> <td>CVE-2026-9679</td> <td>Moderate (5.9)</td> <td>7.28.0</td> <td><a href="https://github.com/nodejs/undici/commit/d0574cc4"><code>d0574cc4</code></a></td> </tr> <tr> <td><a href="https://github.com/nodejs/undici/security/advisories/GHSA-g8m3-5g58-fq7m">GHSA-g8m3-5g58-fq7m</a></td> <td>CVE-2026-11525</td> <td>Low (3.7)</td> <td>7.28.0</td> <td><a href="https://github.com/nodejs/undici/commit/d0574cc4"><code>d0574cc4</code></a></td> </tr> <tr> <td><a href="https://github.com/nodejs/undici/security/advisories/GHSA-35p6-xmwp-9g52">GHSA-35p6-xmwp-9g52</a></td> <td>CVE-2026-6733</td> <td>Low (3.7)</td> <td>7.28.0</td> <td><a href="https://github.com/nodejs/undici/commit/ea8930cf"><code>ea8930cf</code></a></td> </tr> </tbody> </table> <hr /> <h2>High severity</h2> <h3>WebSocket DoS via fragment count bypass — CVE-2026-12151</h3> <p><strong><a href="https://github.com/nodejs/undici/security/advisories/GHSA-vxpw-j846-p89q">GHSA-vxpw-j846-p89q</a></strong> · CWE-400, CWE-770 <strong>Fix:</strong> <a href="https://github.com/nodejs/undici/commit/8cb10f98"><code>8cb10f98</code></a> <em>websocket: limit the number of fragments in a message</em> (part of backport <a href="https://github.com/nodejs/undici/commit/a027a4a0"><code>a027a4a0</code></a> <em>Backport WebSocket maxPayloadSize fixes to v7.x</em>, <a href="https://redirect.github.com/nodejs/undici/pull/5423">#5423</a>)</p> <p>A malicious WebSocket server can stream a large number of small or empty continuation frames. Undici enforced a limit on cumulative payload size but did not limit the <em>number</em> of fragments per message, leading to unbounded memory growth and denial of service.</p> <ul> <li><strong>Affected:</strong> applications using <code>new WebSocket(...)</code> or <code>WebSocketStream</code> against untrusted endpoints.</li> <li><strong>Workaround:</strong> none — upgrade is required.</li> </ul> <h3>TLS certificate validation bypass in SOCKS5 ProxyAgent — CVE-2026-9697</h3> <p><strong><a href="https://github.com/nodejs/undici/security/advisories/GHSA-vmh5-mc38-953g">GHSA-vmh5-mc38-953g</a></strong> · CWE-295</p> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/nodejs/undici/commit/f9eba0ad9134e1c0977848476bba9d49734696e4"><code>f9eba0a</code></a> Bumped v7.28.0 (<a href="https://redirect.github.com/nodejs/undici/issues/5430">#5430</a>)</li> <li><a href="https://github.com/nodejs/undici/commit/a027a4a04c6c055877d1abaf5f60ee4917e7e01f"><code>a027a4a</code></a> Backport WebSocket maxPayloadSize fixes to v7.x (<a href="https://redirect.github.com/nodejs/undici/issues/5423">#5423</a>)</li> <li><a href="https://github.com/nodejs/undici/commit/8cb10f983eb6005dd53f3744d95d3b6d7dbcee0f"><code>8cb10f9</code></a> websocket: limit the number of fragments in a message</li> <li><a href="https://github.com/nodejs/undici/commit/04201f8947041f0f4f2ac865dbdb1677e46a8844"><code>04201f8</code></a> fix: honor requestTls when proxy is SOCKS5</li> <li><a href="https://github.com/nodejs/undici/commit/fcd642ff613ea9030dec87cf622e68d4b1ae9847"><code>fcd642f</code></a> fix(socks5): preserve dispatch backpressure return value (<a href="https://redirect.github.com/nodejs/undici/issues/5166">#5166</a>)</li> <li><a href="https://github.com/nodejs/undici/commit/bc98c97906abf26fa1e959b2f6111b53ade0e18f"><code>bc98c97</code></a> fix(socks5): use configured connector in Socks5ProxyAgent (<a href="https://redirect.github.com/nodejs/undici/issues/5168">#5168</a>)</li> <li><a href="https://github.com/nodejs/undici/commit/9e1c74372a2b27cacd92d27c13a83a6d84f10e0e"><code>9e1c743</code></a> fix(socks5): encode embedded IPv4 tails in IPv6 literals correctly (<a href="https://redirect.github.com/nodejs/undici/issues/5099">#5099</a>)</li> <li><a href="https://github.com/nodejs/undici/commit/376c8be27cb40cc17ccaad6b6ebb317fa7148d65"><code>376c8be</code></a> fix(socks5): enforce authenticated state before CONNECT (<a href="https://redirect.github.com/nodejs/undici/issues/5097">#5097</a>)</li> <li><a href="https://github.com/nodejs/undici/commit/3805b8f8518882991044048c256e005dc3c10a85"><code>3805b8f</code></a> fix(socks5-proxy-agent): use per-origin pools to prevent cross-origin routing...</li> <li><a href="https://github.com/nodejs/undici/commit/85a240551c9feb8b8a0ecc56c84b2b3015add8a9"><code>85a2405</code></a> fix(cache): trim qualified field names</li> <li>Additional commits viewable in <a href="https://github.com/nodejs/undici/compare/v7.25.0...v7.28.0">compare view</a></li> </ul> </details> <br /> Updates `esbuild` from 0.28.0 to 0.28.1 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/evanw/esbuild/releases">esbuild's releases</a>.</em></p> <blockquote> <h2>v0.28.1</h2> <ul> <li> <p>Disallow <code>\</code> in local development server HTTP requests (<a href="https://github.com/evanw/esbuild/security/advisories/GHSA-g7r4-m6w7-qqqr">GHSA-g7r4-m6w7-qqqr</a>)</p> <p>This release fixes a security issue where HTTP requests to esbuild's local development server could traverse outside of the serve directory on Windows using a <code>\</code> backslash character. It happened due to the use of Go's <code>path.Clean()</code> function, which only handles Unix-style <code>/</code> characters. HTTP requests with paths containing <code>\</code> are no longer allowed.</p> <p>Thanks to <a href="https://github.com/dellalibera"><code>@dellalibera</code></a> for reporting this issue.</p> </li> <li> <p>Add integrity checks to the Deno API (<a href="https://github.com/evanw/esbuild/security/advisories/GHSA-gv7w-rqvm-qjhr">GHSA-gv7w-rqvm-qjhr</a>)</p> <p>The previous release of esbuild added integrity checks to esbuild's npm install script. This release also adds integrity checks to esbuild's Deno install script. Now esbuild's Deno API will also fail with an error if the downloaded esbuild binary contains something other than the expected content.</p> <p>Note that esbuild's Deno API installs from <code>registry.npmjs.org</code> by default, but allows the <code>NPM_CONFIG_REGISTRY</code> environment variable to override this with a custom package registry. This change means that the esbuild executable served by <code>NPM_CONFIG_REGISTRY</code> must now match the expected content.</p> <p>Thanks to <a href="https://github.com/sondt99"><code>@sondt99</code></a> for reporting this issue.</p> </li> <li> <p>Avoid inlining <code>using</code> and <code>await using</code> declarations (<a href="https://redirect.github.com/evanw/esbuild/issues/4482">#4482</a>)</p> <p>Previously esbuild's minifier sometimes incorrectly inlined <code>using</code> and <code>await using</code> declarations into subsequent uses of that declaration, which then fails to dispose of the resource correctly. This bug happened because inlining was done for <code>let</code> and <code>const</code> declarations by avoiding doing it for <code>var</code> declarations, which no longer worked when more declaration types were added. Here's an example:</p> <pre lang="js"><code>// Original code { using x = new Resource() x.activate() } <p>// Old output (with --minify)<br /> new Resource().activate();</p> <p>// New output (with --minify)<br /> {using e=new Resource;e.activate()}<br /> </code></pre></p> </li> <li> <p>Fix module evaluation when an error is thrown (<a href="https://redirect.github.com/evanw/esbuild/issues/4461">#4461</a>, <a href="https://redirect.github.com/evanw/esbuild/pull/4467">#4467</a>)</p> <p>If an error is thrown during module evaluation, esbuild previously didn't preserve the state of the module for subsequent module references. This was observable if <code>import()</code> or <code>require()</code> is used to import a module multiple times. The thrown error is supposed to be thrown by every call to <code>import()</code> or <code>require()</code>, not just the first. With this release, esbuild will now throw the same error every time you call <code>import()</code> or <code>require()</code> on a module that throws during its evaluation.</p> </li> <li> <p>Fix some edge cases around the <code>new</code> operator (<a href="https://redirect.github.com/evanw/esbuild/issues/4477">#4477</a>)</p> <p>Previously esbuild incorrectly printed certain edge cases involving complex expressions inside the target of a <code>new</code> expression (specifically an optional chain and/or a tagged template literal). The generated code for the <code>new</code> target was not correctly wrapped with parentheses, and either contained a syntax error or had different semantics. These edge cases have been fixed so that they now correctly wrap the <code>new</code> target in parentheses. Here is an example of some affected code:</p> <pre lang="js"><code>// Original code new (foo()`bar`)() new (foo()?.bar)() <p>// Old output<br /> new foo()<code>bar</code>();<br /> new (foo())?.bar();</p> <p></code></pre></p> </li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/evanw/esbuild/blob/main/CHANGELOG.md">esbuild's changelog</a>.</em></p> <blockquote> <h2>0.28.1</h2> <ul> <li> <p>Disallow <code>\</code> in local development server HTTP requests (<a href="https://github.com/evanw/esbuild/security/advisories/GHSA-g7r4-m6w7-qqqr">GHSA-g7r4-m6w7-qqqr</a>)</p> <p>This release fixes a security issue where HTTP requests to esbuild's local development server could traverse outside of the serve directory on Windows using a <code>\</code> backslash character. It happened due to the use of Go's <code>path.Clean()</code> function, which only handles Unix-style <code>/</code> characters. HTTP requests with paths containing <code>\</code> are no longer allowed.</p> <p>Thanks to <a href="https://github.com/dellalibera"><code>@dellalibera</code></a> for reporting this issue.</p> </li> <li> <p>Add integrity checks to the Deno API (<a href="https://github.com/evanw/esbuild/security/advisories/GHSA-gv7w-rqvm-qjhr">GHSA-gv7w-rqvm-qjhr</a>)</p> <p>The previous release of esbuild added integrity checks to esbuild's npm install script. This release also adds integrity checks to esbuild's Deno install script. Now esbuild's Deno API will also fail with an error if the downloaded esbuild binary contains something other than the expected content.</p> <p>Note that esbuild's Deno API installs from <code>registry.npmjs.org</code> by default, but allows the <code>NPM_CONFIG_REGISTRY</code> environment variable to override this with a custom package registry. This change means that the esbuild executable served by <code>NPM_CONFIG_REGISTRY</code> must now match the expected content.</p> <p>Thanks to <a href="https://github.com/sondt99"><code>@sondt99</code></a> for reporting this issue.</p> </li> <li> <p>Avoid inlining <code>using</code> and <code>await using</code> declarations (<a href="https://redirect.github.com/evanw/esbuild/issues/4482">#4482</a>)</p> <p>Previously esbuild's minifier sometimes incorrectly inlined <code>using</code> and <code>await using</code> declarations into subsequent uses of that declaration, which then fails to dispose of the resource correctly. This bug happened because inlining was done for <code>let</code> and <code>const</code> declarations by avoiding doing it for <code>var</code> declarations, which no longer worked when more declaration types were added. Here's an example:</p> <pre lang="js"><code>// Original code { using x = new Resource() x.activate() } <p>// Old output (with --minify)<br /> new Resource().activate();</p> <p>// New output (with --minify)<br /> {using e=new Resource;e.activate()}<br /> </code></pre></p> </li> <li> <p>Fix module evaluation when an error is thrown (<a href="https://redirect.github.com/evanw/esbuild/issues/4461">#4461</a>, <a href="https://redirect.github.com/evanw/esbuild/pull/4467">#4467</a>)</p> <p>If an error is thrown during module evaluation, esbuild previously didn't preserve the state of the module for subsequent module references. This was observable if <code>import()</code> or <code>require()</code> is used to import a module multiple times. The thrown error is supposed to be thrown by every call to <code>import()</code> or <code>require()</code>, not just the first. With this release, esbuild will now throw the same error every time you call <code>import()</code> or <code>require()</code> on a module that throws during its evaluation.</p> </li> <li> <p>Fix some edge cases around the <code>new</code> operator (<a href="https://redirect.github.com/evanw/esbuild/issues/4477">#4477</a>)</p> <p>Previously esbuild incorrectly printed certain edge cases involving complex expressions inside the target of a <code>new</code> expression (specifically an optional chain and/or a tagged template literal). The generated code for the <code>new</code> target was not correctly wrapped with parentheses, and either contained a syntax error or had different semantics. These edge cases have been fixed so that they now correctly wrap the <code>new</code> target in parentheses. Here is an example of some affected code:</p> <pre lang="js"><code>// Original code new (foo()`bar`)() new (foo()?.bar)() <p>// Old output<br /> new foo()<code>bar</code>();<br /> new (foo())?.bar();<br /> </code></pre></p> </li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/evanw/esbuild/commit/bb9db84c02433fbe37b3509f53f9f3e3cc48725e"><code>bb9db84</code></a> publish 0.28.1 to npm</li> <li><a href="https://github.com/evanw/esbuild/commit/9ff053e53b8eeb990f59355dbea365277ac45ee2"><code>9ff053e</code></a> security: add integrity checks to the Deno API</li> <li><a href="https://github.com/evanw/esbuild/commit/0a9bf2135b67c7e28989a5ba19f0f000805a5ab5"><code>0a9bf21</code></a> enforce non-negative size in gzip parser</li> <li><a href="https://github.com/evanw/esbuild/commit/e2a1a7132058ee067fe736eac15f695861b8654e"><code>e2a1a71</code></a> security: forbid <code>\\</code> in local dev server requests</li> <li><a href="https://github.com/evanw/esbuild/commit/83a2cbfc35809f4fd5152da59572d7bed7739d78"><code>83a2cbf</code></a> fix <a href="https://redirect.github.com/evanw/esbuild/issues/4482">#4482</a>: don't inline <code>using</code> declarations</li> <li><a href="https://github.com/evanw/esbuild/commit/308ad745d824c77bc607603451b257d0f2fd9a38"><code>308ad74</code></a> fix <a href="https://redirect.github.com/evanw/esbuild/issues/4471">#4471</a>: renaming of nested <code>var</code> declarations</li> <li><a href="https://github.com/evanw/esbuild/commit/f013f5f99a015bce92ec48d49181d4ad3177b29b"><code>f013f5f</code></a> fix some typos</li> <li><a href="https://github.com/evanw/esbuild/commit/aafd6e48b1088336a5f5a17e930be7e840d43d8c"><code>aafd6e4</code></a> chore: fix some minor issues in comments (<a href="https://redirect.github.com/evanw/esbuild/issues/4462">#4462</a>)</li> <li><a href="https://github.com/evanw/esbuild/commit/15300c30b5e22f7cfcbed850c246d35095658386"><code>15300c3</code></a> follow up: cjs evaluation fixes</li> <li><a href="https://github.com/evanw/esbuild/commit/1bda0c31d7697c0af44b3ab39b81e599e559a395"><code>1bda0c3</code></a> fix <a href="https://redirect.github.com/evanw/esbuild/issues/4461">#4461</a>, fix <a href="https://redirect.github.com/evanw/esbuild/issues/4467">#4467</a>: esm evaluation fixes</li> <li>Additional commits viewable in <a href="https://github.com/evanw/esbuild/compare/v0.27.7...v0.28.1">compare view</a></li> </ul> </details> <br /> Updates `esbuild` from 0.28.0 to 0.28.1 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/evanw/esbuild/releases">esbuild's releases</a>.</em></p> <blockquote> <h2>v0.28.1</h2> <ul> <li> <p>Disallow <code>\</code> in local development server HTTP requests (<a href="https://github.com/evanw/esbuild/security/advisories/GHSA-g7r4-m6w7-qqqr">GHSA-g7r4-m6w7-qqqr</a>)</p> <p>This release fixes a security issue where HTTP requests to esbuild's local development server could traverse outside of the serve directory on Windows using a <code>\</code> backslash character. It happened due to the use of Go's <code>path.Clean()</code> function, which only handles Unix-style <code>/</code> characters. HTTP requests with paths containing <code>\</code> are no longer allowed.</p> <p>Thanks to <a href="https://github.com/dellalibera"><code>@dellalibera</code></a> for reporting this issue.</p> </li> <li> <p>Add integrity checks to the Deno API (<a href="https://github.com/evanw/esbuild/security/advisories/GHSA-gv7w-rqvm-qjhr">GHSA-gv7w-rqvm-qjhr</a>)</p> <p>The previous release of esbuild added integrity checks to esbuild's npm install script. This release also adds integrity checks to esbuild's Deno install script. Now esbuild's Deno API will also fail with an error if the downloaded esbuild binary contains something other than the expected content.</p> <p>Note that esbuild's Deno API installs from <code>registry.npmjs.org</code> by default, but allows the <code>NPM_CONFIG_REGISTRY</code> environment variable to override this with a custom package registry. This change means that the esbuild executable served by <code>NPM_CONFIG_REGISTRY</code> must now match the expected content.</p> <p>Thanks to <a href="https://github.com/sondt99"><code>@sondt99</code></a> for reporting this issue.</p> </li> <li> <p>Avoid inlining <code>using</code> and <code>await using</code> declarations (<a href="https://redirect.github.com/evanw/esbuild/issues/4482">#4482</a>)</p> <p>Previously esbuild's minifier sometimes incorrectly inlined <code>using</code> and <code>await using</code> declarations into subsequent uses of that declaration, which then fails to dispose of the resource correctly. This bug happened because inlining was done for <code>let</code> and <code>const</code> declarations by avoiding doing it for <code>var</code> declarations, which no longer worked when more declaration types were added. Here's an example:</p> <pre lang="js"><code>// Original code { using x = new Resource() x.activate() } <p>// Old output (with --minify)<br /> new Resource().activate();</p> <p>// New output (with --minify)<br /> {using e=new Resource;e.activate()}<br /> </code></pre></p> </li> <li> <p>Fix module evaluation when an error is thrown (<a href="https://redirect.github.com/evanw/esbuild/issues/4461">#4461</a>, <a href="https://redirect.github.com/evanw/esbuild/pull/4467">#4467</a>)</p> <p>If an error is thrown during module evaluation, esbuild previously didn't preserve the state of the module for subsequent module references. This was observable if <code>import()</code> or <code>require()</code> is used to import a module multiple times. The thrown error is supposed to be thrown by every call to <code>import()</code> or <code>require()</code>, not just the first. With this release, esbuild will now throw the same error every time you call <code>import()</code> or <code>require()</code> on a module that throws during its evaluation.</p> </li> <li> <p>Fix some edge cases around the <code>new</code> operator (<a href="https://redirect.github.com/evanw/esbuild/issues/4477">#4477</a>)</p> <p>Previously esbuild incorrectly printed certain edge cases involving complex expressions inside the target of a <code>new</code> expression (specifically an optional chain and/or a tagged template literal). The generated code for the <code>new</code> target was not correctly wrapped with parentheses, and either contained a syntax error or had different semantics. These edge cases have been fixed so that they now correctly wrap the <code>new</code> target in parentheses. Here is an example of some affected code:</p> <pre lang="js"><code>// Original code new (foo()`bar`)() new (foo()?.bar)() <p>// Old output<br /> new foo()<code>bar</code>();<br /> new (foo())?.bar();</p> <p></code></pre></p> </li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/evanw/esbuild/blob/main/CHANGELOG.md">esbuild's changelog</a>.</em></p> <blockquote> <h2>0.28.1</h2> <ul> <li> <p>Disallow <code>\</code> in local development server HTTP requests (<a href="https://github.com/evanw/esbuild/security/advisories/GHSA-g7r4-m6w7-qqqr">GHSA-g7r4-m6w7-qqqr</a>)</p> <p>This release fixes a security issue where HTTP requests to esbuild's local development server could traverse outside of the serve directory on Windows using a <code>\</code> backslash character. It happened due to the use of Go's <code>path.Clean()</code> function, which only handles Unix-style <code>/</code> characters. HTTP requests with paths containing <code>\</code> are no longer allowed.</p> <p>Thanks to <a href="https://github.com/dellalibera"><code>@dellalibera</code></a> for reporting this issue.</p> </li> <li> <p>Add integrity checks to the Deno API (<a href="https://github.com/evanw/esbuild/security/advisories/GHSA-gv7w-rqvm-qjhr">GHSA-gv7w-rqvm-qjhr</a>)</p> <p>The previous release of esbuild added integrity checks to esbuild's npm install script. This release also adds integrity checks to esbuild's Deno install script. Now esbuild's Deno API will also fail with an error if the downloaded esbuild binary contains something other than the expected content.</p> <p>Note that esbuild's Deno API installs from <code>registry.npmjs.org</code> by default, but allows the <code>NPM_CONFIG_REGISTRY</code> environment variable to override this with a custom package registry. This change means that the esbuild executable served by <code>NPM_CONFIG_REGISTRY</code> must now match the expected content.</p> <p>Thanks to <a href="https://github.com/sondt99"><code>@sondt99</code></a> for reporting this issue.</p> </li> <li> <p>Avoid inlining <code>using</code> and <code>await using</code> declarations (<a href="https://redirect.github.com/evanw/esbuild/issues/4482">#4482</a>)</p> <p>Previously esbuild's minifier sometimes incorrectly inlined <code>using</code> and <code>await using</code> declarations into subsequent uses of that declaration, which then fails to dispose of the resource correctly. This bug happened because inlining was done for <code>let</code> and <code>const</code> declarations by avoiding doing it for <code>var</code> declarations, which no longer worked when more declaration types were added. Here's an example:</p> <pre lang="js"><code>// Original code { using x = new Resource() x.activate() } <p>// Old output (with --minify)<br /> new Resource().activate();</p> <p>// New output (with --minify)<br /> {using e=new Resource;e.activate()}<br /> </code></pre></p> </li> <li> <p>Fix module evaluation when an error is thrown (<a href="https://redirect.github.com/evanw/esbuild/issues/4461">#4461</a>, <a href="https://redirect.github.com/evanw/esbuild/pull/4467">#4467</a>)</p> <p>If an error is thrown during module evaluation, esbuild previously didn't preserve the state of the module for subsequent module references. This was observable if <code>import()</code> or <code>require()</code> is used to import a module multiple times. The thrown error is supposed to be thrown by every call to <code>import()</code> or <code>require()</code>, not just the first. With this release, esbuild will now throw the same error every time you call <code>import()</code> or <code>require()</code> on a module that throws during its evaluation.</p> </li> <li> <p>Fix some edge cases around the <code>new</code> operator (<a href="https://redirect.github.com/evanw/esbuild/issues/4477">#4477</a>)</p> <p>Previously esbuild incorrectly printed certain edge cases involving complex expressions inside the target of a <code>new</code> expression (specifically an optional chain and/or a tagged template literal). The generated code for the <code>new</code> target was not correctly wrapped with parentheses, and either contained a syntax error or had different semantics. These edge cases have been fixed so that they now correctly wrap the <code>new</code> target in parentheses. Here is an example of some affected code:</p> <pre lang="js"><code>// Original code new (foo()`bar`)() new (foo()?.bar)() <p>// Old output<br /> new foo()<code>bar</code>();<br /> new (foo())?.bar();<br /> </code></pre></p> </li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/evanw/esbuild/commit/bb9db84c02433fbe37b3509f53f9f3e3cc48725e"><code>bb9db84</code></a> publish 0.28.1 to npm</li> <li><a href="https://github.com/evanw/esbuild/commit/9ff053e53b8eeb990f59355dbea365277ac45ee2"><code>9ff053e</code></a> security: add integrity checks to the Deno API</li> <li><a href="https://github.com/evanw/esbuild/commit/0a9bf2135b67c7e28989a5ba19f0f000805a5ab5"><code>0a9bf21</code></a> enforce non-negative size in gzip parser</li> <li><a href="https://github.com/evanw/esbuild/commit/e2a1a7132058ee067fe736eac15f695861b8654e"><code>e2a1a71</code></a> security: forbid <code>\\</code> in local dev server requests</li> <li><a href="https://github.com/evanw/esbuild/commit/83a2cbfc35809f4fd5152da59572d7bed7739d78"><code>83a2cbf</code></a> fix <a href="https://redirect.github.com/evanw/esbuild/issues/4482">#4482</a>: don't inline <code>using</code> declarations</li> <li><a href="https://github.com/evanw/esbuild/commit/308ad745d824c77bc607603451b257d0f2fd9a38"><code>308ad74</code></a> fix <a href="https://redirect.github.com/evanw/esbuild/issues/4471">#4471</a>: renaming of nested <code>var</code> declarations</li> <li><a href="https://github.com/evanw/esbuild/commit/f013f5f99a015bce92ec48d49181d4ad3177b29b"><code>f013f5f</code></a> fix some typos</li> <li><a href="https://github.com/evanw/esbuild/commit/aafd6e48b1088336a5f5a17e930be7e840d43d8c"><code>aafd6e4</code></a> chore: fix some minor issues in comments (<a href="https://redirect.github.com/evanw/esbuild/issues/4462">#4462</a>)</li> <li><a href="https://github.com/evanw/esbuild/commit/15300c30b5e22f7cfcbed850c246d35095658386"><code>15300c3</code></a> follow up: cjs evaluation fixes</li> <li><a href="https://github.com/evanw/esbuild/commit/1bda0c31d7697c0af44b3ab39b81e599e559a395"><code>1bda0c3</code></a> fix <a href="https://redirect.github.com/evanw/esbuild/issues/4461">#4461</a>, fix <a href="https://redirect.github.com/evanw/esbuild/issues/4467">#4467</a>: esm evaluation fixes</li> <li>Additional commits viewable in <a href="https://g... _Description has been truncated_ Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
170f506200 |
Fix transport ship's troop count to update when a hydro hits the player. (#4381)
**Add approved & assigned issue number here:** Resolves #4308 ## Description: When nuclear damage reduces a player's troop count, it also affects any transports ships in the water. This works well and is useful to avoid exploiting tranports to avoid hydro damages. `UnitImpl.setTroops()` changes the transports troop count without queuing a unit update. the core value changes, but the client never receives a fresh UnitUpdate unless something else touches the ship. - UnitImpl.ts now emits a UnitUpdate when a unit troop count actually changes. - NukeExecution.ts batches transport ship nuke losses, then applies one final troop update per ship. - Attack.test.ts now asserts the nuke tick includes a transport UnitUpdate with the reduced troop count. ## Please complete the following: - [N/A] I have added screenshots for all UI updates - [N/A] 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: elliotlepley |
||
|
|
5a097317cb |
fix(render): make train tracks visible from farther out
Lower railroad railMinZoom from 4 to 3 so train tracks start rendering at a more zoomed-out level. |
||
|
|
97b689f2d1 |
Add structure dots toggle to graphics settings (#4356)
## What Adds an on/off **Structure dots** toggle to the graphics settings modal (Structure Icons section), controlling whether structures collapse into small dots when zoomed out. ## How The renderer already gates the dots LOD on `structure.dotsZoomThreshold` (structures become dots when `zoom <= threshold`). The debug GUI exposes that threshold as a slider; this surfaces a simple player-facing toggle: - `GraphicsOverrides.ts` — adds `structure.showDots` (boolean) to the override schema. - `RenderOverrides.ts` — when `showDots === false`, `applyGraphicsOverrides` sets `dotsZoomThreshold = 0`. Since zoom is always > 0, the dots LOD never triggers, so structures keep their full icon at every zoom. When enabled (default), the threshold is left untouched. - `GraphicsSettingsModal.ts` — toggle button mirroring the existing Classic icons / Classic level numbers toggles; defaults to On. - `en.json` — `structure_dots_label` / `structure_dots_desc`. The change applies live: a `settings.graphics` change re-runs `applyGraphicsOverrides` onto the live settings object the passes read each frame, and `dotsZoomThreshold` is a per-frame uniform. ## Testing - `tsc --noEmit` clean. - Verified in a headless solo game: the toggle renders (default On), flipping it persists `structure.showDots = false`, and `applyGraphicsOverrides` yields `dotsZoomThreshold` 1.2 when on / 0 when off. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
8536f1bc10 |
fix(events): make donation received events blue instead of green
Donation events were split into DONATION_SENT/DONATION_RECEIVED, and DONATION_RECEIVED was grouped with the green "success" colors. In v31 all player donations (sent and received) were blue. Move DONATION_RECEIVED back to the blue group so both directions render blue again. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
cbf1e5c513 |
Make host lobby start button yellow during countdown
Add a `warning` variant to o-button that reuses the existing `--color-cyber-yellow` brand token, and apply it to the host lobby's start button while the "Starting in Xs" countdown is active. The button stays primary blue in the Waiting/Start states. |
||
|
|
17ee294d5b |
Surface alliance renewal & rejection events in important-events panel (#4354)
## Problem The important-events notification panel splits events into a prominent **tier-1** list and a dim, auto-expiring **tier-2** list (capped at the 4 newest entries). Two alliance notifications were landing in tier-2, making them easy to miss: - **"X wants to renew your alliance"** (`RENEW_ALLIANCE`) — the core simulation still sends this when another player requests a renewal, but it was buried in tier-2, so you couldn't reliably tell whether someone had asked to renew. - **"X rejected your alliance"** (`ALLIANCE_REJECTED`) — also tier-2, so the outcome of a sent request was inconsistent (accepted was prominent, rejected was not). ## Fix Add `MessageType.RENEW_ALLIANCE` and `MessageType.ALLIANCE_REJECTED` to `TIER_1_TYPES` in `EventsDisplay.ts`, so they show prominently alongside the already-tier-1 `ALLIANCE_ACCEPTED` event. No core/simulation changes — the messages were already being emitted; only the client presentation needed fixing. Display styling for both message types already exists in `Utils.ts`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
91cd4214a5 |
Reduce compact map chance in 1v1 from 50% to 20% 🗺️ (#4374)
## Description: Changes the probability of getting a compact map in 1v1 games from 50% (Math.random() < 0.5) to 20% (Math.random() < 0.2) in `MapPlaylist.ts`. Reasoning is feedback on discord. This suggestion thread: https://discord.com/channels/1284581928254701718/1514204284173025301 And this, for example: <img width="634" height="225" alt="image" src="https://github.com/user-attachments/assets/0b4d53de-582c-4e6a-8d18-0007433c12ee" /> Pro players seemingly want to get rid of compact maps completely, but in the thread you can also see people liking them. So lets do 20% 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin |
||
|
|
1ec1f0c5bd |
fix(render): update coastline color dynamically when ocean color changes (#4377)
fix(render): update coastline color dynamically when ocean color changes Resolves #4329 ## Description: Previously, the shoreline water color (`isShoreline && !isLand`) was hardcoded to a static bright blue (`rgb(100, 143, 255)`) in `encodeTerrainTile`. When a user customized the ocean/water color in Settings, the deep ocean changed colors but the shoreline water remained bright blue, causing a jarred, visually mismatched appearance. This PR updates `encodeTerrainTile` in `ColorUtils.ts` to dynamically calculate the shoreline water color by scaling the configured `oceanColor` channels: - Red and Blue channels scale by `1.4` (clamped to `255`). - Green channel scales by `1.08` (clamped to `255`). This scales the coastline water color harmoniously alongside any custom water color settings (e.g. green, red, or dark ocean tones). ## Please complete the following: - [x] I have added screenshots for all UI/rendering updates (Attached `coastline_screenshot.png` showing dynamic color integration) - [ ] I process any text displayed to the user through translateText() and I've added it to the en.json file (N/A — No user-facing text additions) - [ ] I have added relevant tests to the test directory (N/A — WebGL rendering code has no automated test harness) ## Please put your Discord username so you can be contacted if a bug or regression is found: barfires |
||
|
|
b46476384d |
Take off the v32 maps from the "New" category to make room for v33 maps (#4378)
Resolves #4375 ## Description: Remove V32 maps from "new" to make way for v33 maps. This will allow map makers to add their new maps for v33 into "new", with the v32 maps only remaining in the continental and other categories like the rest of the maps. This is a process we will make every update ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: tri.star1011 |
||
|
|
d8395fab46 |
If player is localplayer, set structure border to territory color (#4366)
Resolves #4365 ## Description: Currently the border of icons are using borderColor which is grey for local player <img width="227" height="281" alt="image" src="https://github.com/user-attachments/assets/9e334e19-c5b2-49ca-a85d-4576a5bbc1a9" /> This set it to territory color <img width="187" height="102" alt="image" src="https://github.com/user-attachments/assets/9b9f27f9-69e2-4ae7-9f35-a789b56b45de" /> ## 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 - [ ] I have added relevant tests to the test directory ## Please put your Discord username so you can be contacted if a bug or regression is found: Mr. Box |
||
|
|
758063651d |
Add allowlist for private lobbies (OFM) (#4351)
**Add approved & assigned issue number here:** Resolves #4349 ## Description: 1. **Private-lobby allowlist.** `create_game` accepts an optional `allowedPublicIds`. It's set by whoever creates the lobby (admin-token gated, no client UI), the game server pulls it out of the config so it's never broadcast to clients or written to the game record, and it rejects any joiner whose OF publicId isn't on the list before they take a slot (stickily, so they can't retry on reconnect). Lobbies created without it behave exactly as before. It is off by default Previews: <img width="241" height="140" alt="image" src="https://github.com/user-attachments/assets/30c4e47b-399d-4720-b25b-a04c63668577" /> <img width="982" height="456" alt="image" src="https://github.com/user-attachments/assets/1b5c68b7-9b99-4ccc-b987-e70c8ec25dce" /> <img width="547" height="369" alt="image" src="https://github.com/user-attachments/assets/1623090b-ea2b-4657-9cd8-903fbabca51b" /> I am not able to manually test all of it since it needs to also run the auth API (infra) and actually be connected to disc and whatnot (but still tested the refused flow).. Also, we would need to place some guards and visual error feedback, but since this only would affect casual of players and is more of a improvement to the feature, I will consider it out of scope for now. ## Please complete the following: - [x] I have added screenshots for all UI updates (no UI changes in this PR) - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file (no new user-facing text) - [x] I have added relevant tests to the test directory ## Please put your Discord username so you can be contacted if a bug or regression is found: zixer._ |
||
|
|
93614b274c |
Change css to preview the graphics settings live (#4372)
<img width="1152" height="866" alt="settings" src="https://github.com/user-attachments/assets/dff00f2e-775d-4b7e-8590-855813dcff7d" /> ## Description: Small qol change to be able to preview graphics settings changes ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: Mr. Box |
||
|
|
cd9490a163 |
Fix minimap priority for impassable terrain pathfinding 🗺️ (#4361)
## Description: Changes the minimap downscaling priority in the Go map generator from `Impassable > Water > Land` to `Water > Impassable > Land`. Tristar noticed this bug and reported it in the discord map-maker-area: Previously, any 2x2 block containing even one impassable tile would become impassable on the minimap, completely erasing narrow rivers inside or bordering impassable terrain. Since the water pathfinder runs on the minimap, this caused pathfinding failures near impassable terrain. With water taking highest priority, narrow rivers are preserved on the minimap regardless of surrounding impassable terrain, ensuring the water pathfinder can route through them correctly. ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin |
||
|
|
af1e0c7415 |
fix(account): show "Linked to Google" once a Google account is linked (#4371)
**Add approved & assigned issue number here:** N/A — bug fix for the recently-merged Google OAuth login (#4028), paired with infra PR #378. ## Description: Two bugs were reported after Google login merged. Both stem from the API (`/users/@me`) only reporting the identity used to sign in — fixed in infra PR #378, which must deploy first. This PR is the client half. - **Still says "Link":** a Discord user who linked Google saw the account page still offer "Link Google" (the response never reported `google`). Now that the API reports all linked identities, the account page shows a positive **"Linked to Google (<email>)"** confirmation instead of just hiding the button — so the link visibly succeeds and the user won't re-link (which would replace the prior Google account). - **Avatar replaced by email badge:** signing in via Google dropped the linked Discord profile, so the top bar lost the Discord avatar. This is fixed entirely by the API change (the existing Discord-first logic in `Main.ts`/`hasLinkedAccount` restores the avatar) — no client change needed beyond this PR's linked-state display. ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: jish |
||
|
|
89297bbe9e |
Add nuke fallout color graphics option (#4355)
## What Adds a **Nuke fallout color** option to the in-game graphics settings modal (Effects section), letting players recolor the fallout tint left on territory after a nuke.  ## How Mirrors the existing **Ocean color** override pattern: - `GraphicsOverrides.ts` — adds `staleNukeColor` (hex string) to the `mapOverlay` override schema. - `RenderOverrides.ts` — `applyGraphicsOverrides` parses the hex and writes the renderer's `staleNukeR/G/B` 0–1 float channels (`hexToRgb` yields 0–255, so it divides by 255). - `GraphicsSettingsModal.ts` — new hex-text + native color-picker row, default computed from `render-settings.json`. - `en.json` — `nuke_color_label` / `nuke_color_desc`. The value persists via `UserSettings.graphicsOverrides()` and is cleared by the modal's existing "Reset to defaults". The render debug GUI already exposes the same setting as **Stale Nuke Color** (Map Overlay), so no change was needed there. ## Testing - `tsc --noEmit` clean. - Verified in a headless solo game: the row renders with the green default (`#0d8c12`), changing it persists `mapOverlay.staleNukeColor`, and `applyGraphicsOverrides("#ff0000")` produces `staleNukeR=1, G=0, B=0`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
08b8715667 |
Add black outline to alliance icon for terrain contrast (#4353)
## Problem The green alliance icon above player names blends into similarly-colored terrain — most notably irradiated land, which is the same green — making it hard to spot allied players. ## Fix Add a configurable dark outline to the alliance status icon, rendered in the status-icon shader (the icons come from a pre-baked atlas with no regeneration script, so this is done in-shader rather than by editing the PNG). - **Outline**: an alpha dilation gated to the alliance icon (slot 3). 8-direction sampling of the icon's alpha builds a black halo around its silhouette; interior pixels and all other status icons are untouched. - **No clipping**: the alliance icon's quad is grown outward into the atlas cell's existing transparent padding so the halo isn't clipped at the quad edge. The icon's on-screen size and position are unchanged; 8px of the cell's 16px mipmap-safety padding is preserved. - **Drain stays aligned**: the alliance-expiry drain effect's cut line and faded-icon UVs are remapped into the expanded quad space so the animation still lines up. - **Tunable**: width is driven by `name.statusOutlineWidth` in `render-settings.json` (default 6 texels; 0 disables), with a matching "Status Outline Width" slider in the debug GUI. ## Testing `tsc` and `eslint` pass. Verified in-game: the handshake now reads clearly against irradiated terrain, with the outline rendering fully (no edge clipping) and the drain animation still aligned. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
2f594ebc26 |
Make the important events panel scrollable (#4346)
## What Cap the height of the **Tier 1 (important) events panel** and make it scroll when many events stack up, instead of letting it grow unbounded up the screen. ## Why The less-important (Tier 2) events panel was already height-capped and scrollable, but the important panel had no limit — a burst of important events (chat, nukes inbound, alliance changes, etc.) could push the panel arbitrarily tall. ## Changes (`src/client/hud/layers/EventsDisplay.ts`) - Added `max-h-[30vh] lg:max-h-[40vh] overflow-y-auto` to the important-events container. - Mirrored the existing Tier 2 auto-scroll-to-bottom behavior for the important panel (new `.important-events-container` query + scroll tracking), so the newest important events stay in view rather than being hidden below the fold. If the player scrolls up, auto-scroll pauses (same as Tier 2). ## Testing Verified in the live game by injecting 15 important events: - Panel is height-capped and scrollable (`scrollHeight 540 > clientHeight 400`). - Auto-scrolls to the newest (`scrollTop` pinned to bottom); events 5–15 visible, older ones reachable by scrolling up. lint clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
08af8470fa |
Fixed factory ghost radius (#4337)
> **Before opening a PR:** discuss new features on [Discord](https://discord.gg/K9zernJB5z) first, and file bugs or small improvements as [issues](https://github.com/openfrontio/OpenFrontIO/issues/new/choose). You must be assigned to an `approved` issue — unsolicited PRs will be auto-closed. **Add approved & assigned issue number here:** Resolves #4323 ## Description: Made stations use euclidean distance for radius for checking if other stations are close enough, removed redundant if check and unneeded config <img width="1920" height="1080" alt="Screenshot from 2026-06-18 14-19-48" src="https://github.com/user-attachments/assets/a84f29f8-0cc1-46ea-9b96-3d70d6b0b20a" /> ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: tktk1234567 |
||
|
|
805f0968b1 |
Add impassable terrain 🗺️ (#4340)
## Description: Relates to #3725 Adds a new **Impassable** terrain type that enables non-rectangular maps and creates impassable barriers on the map. Painted with pure black (`#000`) in the map editor's `image.png`. **Encoding:** Impassable terrain is encoded in the binary format as `isLand=1, magnitude=31` (previously unused). The Go map generator detects `#000` pixels and produces this encoding. The map generator's minimap downscaling gives impassable highest priority (Impassable > Water > Land). Thumbnails render impassable as transparent so the map picker background shows through. **Rendering:** Impassable tiles render as the map background colour (`rgb(60, 60, 60)`, matching `gl.clearColor` in `Renderer.ts`), making them visually indistinguishable from the area outside the map quad. This enables maps to appear non-rectangular. **Gameplay restrictions:** Impassable terrain cannot be: - Owned (`conquer()` throws) - Attacked (`AttackExecution` skips impassable tiles in both `tick()` and `addNeighbors()`) - Nuked (targeting rejected in `nukeSpawn()`, blast radius filtered in `tilesToDestroy()`) - Spawned on (nations, human players, and structures all reject impassable tiles) - Converted to water (guarded in `WaterManager` and `setWater()`) **Nuke trajectories:** Nuke trajectories cannot cross impassable terrain, matching the existing map-border enforcement. This is checked at launch time in `NukeExecution.tick()`. The client-side trajectory preview turns red with a red X where the arc crosses impassable terrain (reusing the existing SAM-intercept visual pipeline in `NukeTrajectory.ts`). The nuke ghost preview is completely hidden when hovering over impassable terrain (same as hovering outside the map). https://github.com/user-attachments/assets/ff131146-9749-41e0-892a-617e5cd16c54 Impassable terrain is transparent on the thumbnail: <img width="213" height="152" alt="Screenshot 2026-06-18 211640" src="https://github.com/user-attachments/assets/ede16f8c-9239-4ab1-be5d-0ba81cce5e9e" /> Tested with water nukes, made sure there is no water depth gradient near the impassable terrain, just like at the world border: <img width="774" height="771" alt="Screenshot 2026-06-18 212348" src="https://github.com/user-attachments/assets/4429069d-911b-48e8-91e3-7307d42c9397" /> Models used: GLM 5.2 and MiMo 2.5 Pro 😄 ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin |
||
|
|
6e892839e8 |
Ofm tournament - Log Final standings and Per-Kill eliminations (#4350)
**Add approved & assigned issue number here:** Resolves #4349 ## Description: The infra related PR is linked to this one and would need to be pushed first (376) Two changes for organized/tournament matches: 1. **Final standings.** `setWinner` snapshots each player's tiles owned at game end into `PlayerStats` (`finalTiles`). It's a deterministic integer captured in the sim, so it's replay-safe and rides into the existing game record. This lets standings be derived directly (winner, then surviving players by territory, then eliminated players by when they died) without re-simulating, which matters because a domination win ends with many players still alive. 2. **Per-kill log**. Records, per player, which humans they eliminated and at what tick (kills on PlayerStats). This lets standings attribute each kill to the victim's final placement, and gives a deterministic kill graph for integrity review. Hooked once in conquerPlayer (the single elimination funnel), humans only. Additive optional field that rides the existing game record, no archive or wire changes. These are off by default with no effect on normal play. ## Please complete the following: - [x] I have added screenshots for all UI updates (no UI changes in this PR) - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file (no new user-facing text) - [x] I have added relevant tests to the test directory ## Please put your Discord username so you can be contacted if a bug or regression is found: zixer._ |
||
|
|
ff5eb78689 |
Login with Google — client UI (#4028) (#4279)
Resolves #4028 (client half — backend is openfrontio/infra#368, which must be deployed first). ## Description: Adds "Login with Google" to the client, alongside the existing Discord login. Companion to the backend PR (openfrontio/infra#368). - `Auth.ts` — `googleLogin()` (full-page redirect to `/auth/login/google?redirect_uri=…`, mirrors `discordLogin()`). - `ApiSchemas.ts` — `GoogleUserSchema` + optional `user.google` on `UserMeResponseSchema`. - `AccountModal.ts` — a "Login with Google" button (Google brand guidelines: white surface, dark text, the multicolor "G" mark) in the login options, and the logged-in view now renders a Google-authenticated user's email (also added `google` to `isLinkedAccount()`). - `en.json` — `main.login_google`. - `resources/images/GoogleLogo.svg` — the Google "G" mark. > **Draft.** Depends on infra#368 being deployed (the button hits the live `/auth/login/google`). ## Please complete the following: - [x] I have added screenshots for all UI updates <!-- TODO: add screenshot of the Google button --> - [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 <!-- no client tests exist for AccountModal/Auth; verified via tsc --noEmit + eslint. Backend behaviour is covered in infra#368 --> ## Please put your Discord username so you can be contacted if a bug or regression is found: jish |
||
|
|
21291b9fa3 |
Add trade ship captured event with toggle setting (#4344)
## What Notify a player when one of their trade ships is captured. The alert appears in the **less-important (top) events tier** and is gated behind a new in-game setting (on by default). ## Why Previously there was no notification to the player who *lost* a trade ship — only the capturer got a transient +gold pip on the ship's arrival. This surfaces the loss to the victim, while letting players opt out if they find it noisy. ## Changes - **`src/core/execution/TradeShipExecution.ts`** — On capture detection, emit a display message (`events_display.trade_ship_captured`, type `UNIT_DESTROYED`) to the original owner. Fires once, guarded by the existing `wasCaptured` flag. `UNIT_DESTROYED` is not a Tier-1 type, so it lands in the top/less-important tier. - **`src/client/hud/layers/EventsDisplay.ts`** — Suppress the message when the setting is off, following the existing key-based filter pattern. - **`src/core/game/UserSettings.ts`** — New `tradeShipCapturedEvents()` getter (default `true`) + `toggleTradeShipCapturedEvents()`. - **`src/client/hud/layers/SettingsModal.ts`** — New toggle in the in-game settings modal. - **`resources/lang/en.json`** — New `events_display.trade_ship_captured` and `user_setting.trade_ship_captured_label`/`_desc` keys. - **`tests/core/executions/TradeShipExecution.test.ts`** — Tests that the notification is sent to the original owner with the right args and only once across ticks. ## Notes - The setting is gated client-side (in `EventsDisplay`), keeping `src/core` free of client-local localStorage settings — consistent with how display events are already filtered there. - Reused `MessageType.UNIT_DESTROYED` (red/"loss" styling) rather than adding a new message type, to keep the change minimal. Happy to add a dedicated type/color if preferred. ## Testing - `npx vitest tests/core/executions/TradeShipExecution.test.ts --run` — 7 passed - lint clean, no type errors 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
9881b118e4 |
Fix anonymous-names setting not hiding names on the map (#4345)
## Problem Enabling the **hidden names** (anonymous names) setting hid names in the leaderboard/HUD but **not on the map**. The GL name renderer (`NamePass`) drew `slot.static.displayName` — always the real name — and never consulted `userSettings.anonymousNames()`. The HUD works because it calls `PlayerView.displayName()` (which honors the setting) on each render, but the names baked into the GPU texture bypassed that path entirely. ## Fix Push the *resolved* name into the renderer instead of the raw static name: - **`WebGLFrameBuilder.syncPlayers`** registers each player with `displayName: p.displayName()` (honors the setting) instead of `static.displayName`. Covers enabling the setting before a game and players who join after a toggle. - **`WebGLFrameBuilder.refreshNames` → `MapRenderer` → `Renderer` → `NamePass.refreshNames`** is a new path that re-resolves cached names and forces a re-upload (resets `slot.nameLen = 0`, which also recomputes the name half-width so it stays centered). - **`ClientGameRunner`** listens for the `settings.anonymousNames` change event and calls `refreshNames`, mirroring the existing territory-patterns live toggle. ## Behavior - Enabled before a game → players register with anonymous names. - Toggled mid-game → map names flip to/from anonymous on the next sim tick (~100ms), matching the leaderboard. - Your own name is unaffected (unchanged — `PlayerView` maps the local player's anonymous name to their real name). ## Testing `tsc --noEmit` passes for all edited files. This is a WebGL rendering change with no straightforward unit test; verified by tracing the data flow (resolved name → cached `slot.static.displayName` → re-upload on dirty). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
58e8a5fabd |
Fix ocean color change reverting nuke-created water to land (#4343)
## Problem Changing the ocean/water color in **Graphics settings** repaints the terrain — and any water tiles created by water nukes (land → water) snap back to their original land appearance. ## Root cause `TerrainPass` captures the `terrainBytes` buffer at construction and reuses it in two places: - `setOceanColor()` does a **full** terrain texture re-upload from `terrainBytes` when the ocean color changes. - `applyTerrainDelta()` applies live land→water nuke conversions, but only wrote to the **GPU texture** — never back into `terrainBytes`. So the CPU buffer stayed frozen at the map's original terrain. Changing the ocean color rebuilt the whole texture from that stale buffer, reverting every nuke crater to land. ## Fix Write each delta byte back into `terrainBytes` inside `applyTerrainDelta()`, so the buffer stays the live source of truth and full re-uploads reflect conversions. ```ts this.terrainBytes[ref] = bytes[i]; ``` The indexing already lines up — `terrainBytes` is indexed by linear ref (`y * mapW + x`), the same `ref` the delta loop iterates. The buffer is only otherwise read once at construction by `RailroadPass`/`TerrainPass` to seed GPU textures (which copy), so mutating it has no side effects elsewhere. ## Testing The WebGL passes have no unit-test harness (they need a live GL context), so this isn't covered by an automated test. Verified by reasoning through the data flow; can confirm in-game by nuking land into water and then changing the ocean color. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
117fa43947 |
Fix nuke preview showing teammate SAMs as threats (#4342)
## Problem In the nuke trajectory preview, the SAM-intercept **"X"** marker was drawn over **teammates'** SAMs — implying their SAM would shoot down your missile. It shouldn't: like allies, a teammate's SAM never engages your nuke. The bug only affected teammates; allies already worked. ## Cause The preview built its threat set from `myPlayer.allies()` only — formal alliances — and never considered teammates. That diverged from the sim ([`SAMLauncherExecution.ts`](src/core/execution/SAMLauncherExecution.ts#L118-L134)), which skips any nuke whose owner it's `isFriendly()` with (**same team OR allied**). ## Fix `samThreatensNukePreview` now takes a teammate set and excludes teammates **unconditionally**. The subtlety: allies keep the existing *betrayal* exception — a strike close enough to break the alliance makes that ally's SAM engage at launch (`listNukeBreakAlliance`, the same function the sim uses). Teammates get **no** such exception, because a strike can break an alliance but never a team relationship. So even a player who is both a teammate *and* a betrayed ally is correctly left off the threat set. ## Notes - The sim has an "aftergame fun" exception where teammate SAMs *do* target teammate nukes once there's a winner. The preview only appears while aiming a buildable mid-game (no winner yet), so that case doesn't apply here. ## Tests Updated `samThreatensNukePreview` unit tests for the new signature and added coverage for: teammate excluded, and teammate stays excluded even when listed as betrayed. All 11 tests pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
167acb1fae |
update location of color mapping in map generator readme (#4325)
Resolves #4326 ## Description: Updates map-generator readme for terrain color info locations modified in v32. ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: tidwell |
||
|
|
16996d489c |
Hide clan tag input on CrazyGames (#4341)
## Summary Clans aren't supported on CrazyGames, so don't let players set a clan tag there. - Tag the clan tag input wrapper with the existing `no-crazygames` class so Main.ts's hiding logic removes it on CrazyGames, matching how other CrazyGames-hidden elements work. - Guard loading the stored clan tag (`loadStoredUsername`) so a tag saved on the main site isn't silently submitted in the handshake while on CrazyGames — CSS hiding alone wouldn't prevent that. - Guard storing the tag (`validateAndStore`) so a returning user's saved tag isn't clobbered with an empty value during a CrazyGames session. ## Testing - `npx tsc --noEmit` — clean (no UsernameInput errors) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
0e7c33a594 |
Cap renderer device-pixel-ratio at 2 (#4339)
## What
Routes every renderer call site that read `window.devicePixelRatio`
through a single `renderDpr()` helper that caps the value at **2**.
```ts
export function renderDpr(): number {
return Math.min(window.devicePixelRatio || 2, 2);
}
```
## Why
On very high-DPI displays (DPR 3, common on phones) the WebGL backing
store was sized at 3× CSS pixels — ~9× the fragment work of 1× — for a
marginal visual gain over 2×. Capping at 2 keeps retina (DPR 2)
pixel-perfect while clamping the 3× case.
## How it stays correct
DPR isn't just the canvas size — it's one coordinate system shared by:
- the canvas backing-store size (`Renderer.resize`)
- the camera's screen↔world math (`Camera.resize` / `screenToWorld` /
`worldToScreen`)
- the camera zoom scale (`ClientGameRunner.syncCamera`)
- the constant-CSS-pixel-size world text (`WorldTextPass`)
These must all use the same DPR value or pointer hit-testing and text
sizing drift. Routing them through one helper guarantees that. The
diagnostics reporter (`Diagnostic.ts`) is intentionally left reading the
real hardware DPR, since its job is to report the actual device.
## Test
- `tsc --noEmit` clean for all touched files (one pre-existing unrelated
`marked` types error remains on `main`).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
ca5342d6bf |
Render spawn overlay with instancing to support large lobbies (#4322)
## Problem The spawn-phase overlay stored every human's spawn center in GLSL **uniform arrays** (capped at `MAX_SPAWNS = 32`) and looped over all of them **per screen pixel** in a fullscreen pass. In lobbies with more than 32 humans, centers past the cap were silently dropped in join order — so a few seconds into the spawn phase the **local player's own ring could disappear while the phase was still active**. Team modes make this worse: `playerTeams` can be a raw team count, so a single team can have far more than 32 members, all of which need rings. The two walls that blocked simply raising the constant: - **Uniform arrays cap out ~96** against WebGL2's 224-vec4 fragment floor — 1024 would never link. - The **fullscreen per-pixel loop** over every spawn is `O(pixels × spawns)` — raising the cap makes it a GPU hazard during the spawn phase. ## Fix Rewrite `SpawnOverlayPass` to draw **one instanced quad per spawn center**, sized to that center's influence radius (mirroring `SAMRadiusPass`). This removes the uniform-array limit and the per-pixel loop, so cost scales with the number of spawns rather than screen area, and the overlay supports the renderer's full ~1024-player ceiling. Instances are ordered **enemies → teammates → self** so the local player's ring composites on top under normal alpha blending. Self/teammate render as breathing rings; enemies render as tile-fill highlights on unowned tiles — identical visuals and render-settings to before. ## Changes - `gl/passes/SpawnOverlayPass.ts` — instanced rendering via `DynamicInstanceBuffer` + `drawArraysInstanced`; no `MAX_SPAWNS` cap. - `shaders/spawn-overlay/spawn-overlay.frag.glsl` — per-instance (kind-dispatched) instead of a uniform-array loop; self white→color pulse moved into the shader. - `shaders/spawn-overlay/spawn-overlay.vert.glsl` — new instanced vertex shader. ## Testing - `tsc` (full project) + `eslint` clean. - Headless WebGL run: shaders **compile and link** (game starts normally with 123 players), and the genuine `updateSpawnOverlay → update() → drawArraysInstanced()` path renders self/teammate rings and enemy tile highlights with **no GL errors**. - ⚠️ Not yet verified end-to-end in a real 30+ human FFA lobby (the original repro) — that needs multiple real clients. The instanced draw path and rendering were confirmed in singleplayer with the overlay force-activated. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
661d96ba28 |
Fix structure cost double-counting units under construction (#4320)
## Problem
The ghost/build-menu price of a structure can show the wrong (inflated)
cost. Concretely: a player who owns a **captured** city and then starts
building their **first** city sees the 3rd-city price (**500k**) for
that build instead of the 2nd-city price (**250k**).
## Root cause
Structure cost scales as `2^(units built) × base` (city: 125k / 250k /
500k …), counted via:
```ts
Math.min(player.unitsOwned(type), player.unitsConstructed(type))
```
The `Math.min` is deliberate — it caps the count at how many you've
actually **built**, so **captured** units (owned but not built) don't
inflate the price.
`unitsConstructed()` defeated that by double-counting in-progress
builds:
```ts
const built = this.numUnitsConstructed[type] ?? 0; // already includes the building unit
let constructing = 0;
for (const unit of this._units) {
if (unit.type() !== type) continue;
if (!unit.isUnderConstruction()) continue;
constructing++; // counts the SAME unit again
}
return constructing + built; // doubled
```
`recordUnitConstructed()` is called in `buildUnit()` the moment the unit
is created — while it is still under construction — so
`numUnitsConstructed` already accounts for in-progress builds. The extra
loop counted them a second time.
With one captured city + one city under construction: `unitsOwned = 2`,
double-counted `unitsConstructed = 2`, so `Math.min(2, 2) = 2` → 500k.
Without the double-count it's `Math.min(2, 1) = 1` → 250k. ✅
The redundant loop is a leftover from #2378, which removed the separate
`UnitType.Construction` unit. Back then in-progress builds were a
distinct unit type **not** recorded in `numUnitsConstructed`, so the
loop was needed; afterward it became a pure double-count. This is a
long-standing latent bug — present identically on `v31` — not a recent
regression.
## Fix
`unitsConstructed()` now just returns `numUnitsConstructed[type]`, which
already includes under-construction builds.
## Tests
`tests/economy/ConstructionCost.test.ts` covers both:
- pure case (first city under construction) → still 250k
- captured city + first city under construction → was 500k, now 250k
(fails without the fix with `expected 2 to be 1`)
All related suites (economy, PlayerImpl, nation structure behavior,
upgrades, MIRV pricing, stats) — 144 tests — pass.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v0.32.0
|
||
|
|
305534cc65 |
Dispose WebGL renderer when a game stops 🧹 (#4295)
## Problem `ClientGameRunner.stop()` tore down the worker, network, and sound, but left the `MapRenderer` (and its WebGL context), the WebGL canvas, the input overlay, and the self-driving RAF loop in place. When you exit a game via the **Exit button** or browser **back**, the page navigates to `/`, so the browser reclaims everything — that path is fine. But you can start a new game **without** a reload: matchmaking and joining another lobby go through `handleJoinLobby`, which calls `lobbyHandle.stop(true)` then `joinLobby()` on the same document. The old WebGL context stayed alive (the never-cancelled RAF kept it referenced, so it wasn't even GC'd), and each new game stacked another context. After a few games, mobile browsers hit their WebGL context limit — matching the repro in #4267. ## Fix `stop()` now disposes the renderer: - cancels the self-driving RAF loop and disconnects the frame-loop resize observer - disposes the `MapRenderer` (frees all GPU resources) - removes the WebGL canvas and the input overlay from the DOM `GPURenderer.dispose()` additionally calls `WEBGL_lose_context.loseContext()` so the context is released promptly instead of waiting on unreliable GC. The territory-patterns settings listener is wired to the existing graphics `AbortController` so it no longer outlives the disposed view. The cleanup runs unconditionally in `stop()` (a superseded join can stop before the game becomes active) and is idempotent against repeated `stop()` calls. Fixes #4267 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>v0.32.0-beta6 |
||
|
|
64409cae4d |
Animate HUD troop/population bars with transform instead of width (#4319)
## Problem The troop and population ratio bars in `ControlPanel` and `PlayerInfoOverlay` update their inline `width` on every game tick, with a `transition-[width] duration-200` to smooth the change. But `width` is a layout property — animating it forces the browser to **recalculate layout for the surrounding HUD components every animation frame**. Since the width changes every tick, this kept the whole HUD in a near-constant relayout loop and showed up as jank. ## Fix Keep the smooth animation, but drive it with `transform` (GPU-composited, no layout) instead of `width`: - Replace the two flex `width: %` segments with absolutely-positioned, full-width bars. - Segment 1: `transform: scaleX(green/100)` anchored to the left edge (`origin-left`). - Segment 2: `transform: translateX(green%) scaleX(orange/100)` so it stays flush against the first segment. - Animate with `transition-transform duration-200 ease-out`. Because `transform` is composited rather than laid out, the bars animate smoothly **without** triggering the per-frame HUD relayout. The segments are now always mounted (`scaleX(0)` when empty) instead of conditionally rendered, which also prevents the transition from resetting as values cross zero. Files: - `src/client/hud/layers/ControlPanel.ts` (mobile + desktop troop bars: malibu-blue / aquarius) - `src/client/hud/layers/PlayerInfoOverlay.ts` (sky-700 / malibu-blue) A grep confirmed these were the only `transition-[width]` usages in the client. ## Testing - `eslint --fix` / prettier ran clean via the pre-commit hook. - CSS-only change; no sim/behavioral logic touched. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
83cd864018 |
Show rail ghost for initial factory 🚂 (#4294)
## Problem Fixes #4284. When you build a factory in an area with **no pre-existing factory** (e.g. just a city nearby), no rail ghost preview appeared — even though building the factory *would* lay rail lines connecting it to that city. ## Root cause `computeGhostRailPaths` in `RailNetworkImpl.ts` had two factory-hostile assumptions: 1. It bailed out early unless a `Factory` was already in range (`hasUnitNearby(..., UnitType.Factory)`). 2. It only matched neighbors that were *already* train stations (`findStation(...)` → skipped if null). But a **Factory** always becomes a station itself and *promotes* nearby City/Port/Factory into the rail network (see `FactoryExecution`). So it needs no pre-existing factory, and its neighbors won't be stations yet on first build. A **City/Port** only joins the network when a factory already exists (`CityExecution`/`PortExecution`) — so their behavior is correctly left unchanged. ## Fix - Skip the "factory must be nearby" gate when the placed unit is itself a `Factory`. - For a factory build, pathfind to nearby City/Port/Factory even if they aren't stations yet. City/Port keep connecting only to existing stations. ## Tests Added two cases to `RailNetwork.test.ts` (factory connects with no pre-existing factory; city still doesn't without one). All 25 tests pass. ## Note on scope As @Katokoda noted on the issue, a fully build-exact preview (neighboring structures also connecting to *each other*, merging existing networks, etc.) is larger and order-dependent. This PR resolves the reported bug — the initial factory now shows its rail ghost — and leaves the exact-match cascade as a separate follow-up. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
678112492c |
Fix per-frame layout jank when focusing a toggle-input-card field (#4314)
## Problem
Focusing the number field of a `toggle-input-card` (Game Timer / Gold
Multiplier / Starting Gold, in both the single-player and host-lobby
modals) cost several ms of layout/paint **every tick** for as long as
the field stayed focused.
## Root cause
The input was rendered **conditionally** — `${this.checked ?
html`…<input>…` : nothing}`. Enabling a toggle therefore **freshly
inserts** the `<input>` into the DOM, and **focusing a just-inserted
input** is what forced the per-frame layout/paint. An input that was
already present in the DOM doesn't do this.
## Fix
Keep the input **permanently mounted** and toggle a `hidden` class when
unchecked, instead of conditionally rendering it. Focusing it is then
always focusing an element that was already there. Because both modals
share `<toggle-input-card>`, this single change fixes both.
Also restores the **autofocus + select** of the field on enable (it had
been removed earlier while chasing this bug) — safe now that the input
isn't freshly inserted.
No other UX change: the toggle behavior, checkmark, styling, and all
three cards behave identically.
## Testing
Hard-reload, then in both the Solo and Host-lobby modals, enable each of
Game Timer / Gold Multiplier / Starting Gold, type a value, and keep the
field focused — smooth, no per-frame jank, and the field autofocuses on
enable.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
bb464538d0 |
Add Fallout effects toggle to graphics settings ☢️ (#4313)
## Summary Adds a **"Fallout effects"** toggle to the *Effects* section of the graphics settings modal, letting players disable the nuclear fallout visuals (useful for performance). Fallout is rendered by two passes — the broiling green **bloom** on irradiated territory and its additive **light** contribution in day/night mode. The bloom pass was already gated by `passEnabled.falloutBloom`, but the light pass had no gate. This adds a `passEnabled.falloutLight` flag and a single user-facing `passEnabled.fallout` graphics override that drives both together. ## Changes - **`RenderSettings.ts` / `render-settings.json`** — new `passEnabled.falloutLight` flag (default `true`). - **`LightmapPass.ts`** — gate the fallout light pass behind `passEnabled.falloutLight`. - **`GraphicsOverrides.ts`** — add `fallout: z.boolean()` to the `passEnabled` override group. - **`RenderOverrides.ts`** — apply `passEnabled.fallout` to both `falloutBloom` and `falloutLight`. - **`GraphicsSettingsModal.ts`** — `currentFallout()` / `onToggleFallout()` + a toggle button (mirrors the existing Special Effects toggle). - **`en.json`** — `graphics_setting.fallout_label` / `fallout_desc`. ## Testing - `tsc --noEmit` passes; JSON files validated. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
e6256e6269 |
Flip structure icon glyph to light on dark territory 🌑 (#4312)
## Problem
In the structure render pass, when **classic icons** are enabled the
inner icon glyph is tinted with a *darkened version of the player's fill
color* (`uIconDarken = 0.3`). When a player's territory color is already
dark, both the structure shape and its glyph render dark, so the icon
blends into the shape and the dark territory behind it — making it
effectively unreadable.
(With non-classic icons the glyph is already the light `uIconColor`, so
only the classic path was affected.)
## Fix
In `structure.frag.glsl`, when classic icons are active, compute the
fill's perceptual luminance and flip the glyph to the light icon color
(`uIconColor`, white by default) when the fill is too dark:
```glsl
vec3 glyphColor = uIconColor;
if (uIconDarken > 0.0) {
float fillLum = dot(fillColor.rgb, vec3(0.299, 0.587, 0.114));
glyphColor = fillLum < 0.25 ? uIconColor : darken(fillColor.rgb, uIconDarken);
}
```
The non-classic path is unchanged. The change is contained to the shader
— no new uniforms or plumbing.
## Notes
- The `0.25` luminance threshold is hardcoded in the shader to keep the
change surgical. It could be promoted to a `render-settings.json` knob
if preferred.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
6c84919801 |
Smooth nuke point-light position per frame in ambient mode (#4311)
## Summary Follow-up to #4255. That PR made nuke **sprites** glide per render frame — `UnitPass.drawMissiles` lerps each nuke's `lastPos→pos` by wall-clock progress through the current tick. But in ambient/night mode the glow *behind* a nuke comes from a separate pass, `PointLightPass`, whose instance buffer is packed once per tick in `updateLights()` from the raw `unit.pos`. Its per-frame `draw()` (run every frame via `LightmapPass`) only set uniforms and issued the instanced draw — it never repositioned the lights. So the sprite moved at 60fps while its light jumped once per 100ms tick. ## Fix Mirror `UnitPass`'s smoothing in `PointLightPass`: - `updateLights()` records a `smoothSegs` tuple `(lightIdx, lastX, lastY, x, y)` for each `SMOOTHED_NUKE_TYPES` unit whose `lastPos !== pos`, and stamps `lastUnitsUpdateMs`. - A new `applySmoothing()`, called at the top of `draw()`, lerps those lights by wall-clock tick progress (`(now - lastUnitsUpdateMs) / tickIntervalMs`, clamped to 1) and re-uploads **only** the affected instances. Unlike `UnitPass` (which re-uploads its tiny missile buffer wholesale), the light buffer can hold thousands of static structure lights, so a full per-frame re-upload would be wasteful. - `tickIntervalMs` comes from a new `config` constructor param, wired through in `Renderer.ts` (the same `config` already passed to `UnitPass`). The light now uses the exact same `lastPos→pos` endpoints and alpha as the sprite, so the two track together. ## Test plan - `npx tsc --noEmit`, eslint, and prettier all clean. - `npx vitest tests/client/render --run` — 40 passed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
8a8079b979 |
Fix parallel bot boat attacks of nations 🐛 (#4297)
## Description: When a nation attacked multiple bots via boat attacks in parallel, each boat attack computed its troop allocation independently using `player.troops() / 5` without subtracting `botAttackTroopsSent`. The cumulative troop commitment could exceed the nation's actual troop count, and when the queued `AttackExecution`s ran `init()`, they drained the nation to zero. Planetary Realignment found this bug by accident, here Russia has only 39 troops: <img width="1189" height="654" alt="image" src="https://github.com/user-attachments/assets/07b85e00-6734-4ddd-a16e-fe53309e0ef8" /> The land attack path already handled this correctly. The bug in `sendBoatAttack` was introduced by #3786, which made nations see and attack enemies across rivers via boats, and changed `attackBots()` from `.neighbors()` to `.nearby()`. So the bug was on prod for the entirety of v31. This fix extracts the shared attack troop calculation (reserve, bot-aware allocation, troopSendCap, isAttackTooWeak, emoji) into a new `calculateAttackTroops` method, with a callback for the non-bot troop default (land: `player.troops() - targetTroops`, boat: `player.troops() / 5`). Bot targets in both paths now go through the same reserve-aware calculation. ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin |
||
|
|
6833cef7bc |
Tweak render settings to look more like v31
Lower territory saturation, highlight thickness, and border darkening to bring the rendered map closer to the v31 look. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
1ad71b9cfa |
Fix hover-highlight inner border lagging during tile changes (#4303)
## Problem
When you hover over a territory, it highlights with a band that is
`highlightThicken` (default **2**) tiles deep — the edge plus 2 interior
rings, computed via a Chebyshev expansion in `border-compute.frag.glsl`.
Starting a hover triggers a full border recompute, which paints the band
correctly. But while you keep hovering and tiles change owner (territory
growing/shrinking, combat at the front), only the cheap **incremental**
scatter path runs. `BorderScatterPass.pushWithNeighbors` repainted only
the changed tile **+ its 4 cardinal neighbors** (radius 1) — fine for
normal borders, but not for the highlight band. A changed tile affects
the thickening of *every* highlight-owner tile within `highlightThicken`
of it, and those interior tiles were never repainted, so the **inner
edge of the highlight band stayed stale** ("the inside border is not
getting updated"). This was a documented trade-off in the class comment.
## Fix
When a highlight is active, `pushWithNeighbors` now repaints a Chebyshev
**box of radius `highlightThicken`** around each changed tile (the box
subsumes the cardinal cross, so normal borders still update). With no
highlight active it stays on the cheap 5-point cross, preserving the
pass's O(dirty-tiles) scaling. The extra cost (~25 vs 5 points/tile at
default) only applies while actually hovering.
## Testing
Hover over a territory while it grows/shrinks (early-game expansion or a
war front) and confirm the inner edge of the highlight band now tracks
the moving border instead of lagging.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
|