chore(sync): merge main into develop - #8106
Merged
Merged
Conversation
…et (#8088) # Summary Restore Explorer's font size: <img width="3440" height="1139" alt="Screenshot 2026-09-03 at 14 29 40" src="https://github.com/user-attachments/assets/f7530c91-32a0-4e77-8c13-aa14a848dde1" /> # To Test 1. Open Explorer Solvers page 2. Check the font size in the inputs * Should be the same as current production 3. Check the rest of the Explorer app * Fonts size should remain as it was # Self-checks - [x] I have read [CONTRIBUTING.md](../CONTRIBUTING.md) - [x] I have manually tested changes on Vercel preview deployment - [x] I have done self-review and (or) AI review - [x] I have addressed all comments from @coderabbitai - [x] I have less than three open PRs/Stacks in this repo at the moment of creating this PR <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Restored the default readable font size for buttons, text areas, dropdowns, and most text input fields in Explorer. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
# Summary Prevent duplicated RWA token lists. <img width="1193" height="1059" alt="image" src="https://github.com/user-attachments/assets/0549da36-0cb3-4954-acb9-efad21961ed6" /> 1. Dedupes token lists by path rather than commit id. New lists with a different commit will always differ if checked fully 2. On every load, looks for stale lists and removes them. Otherwise we'd have a growing list on every CMS update. # To Test 1. Load existing localStorage from somewhere else, like staging 2. Open the list selector * Not duplicates * No lists (if in a not allowed location) ## AI description <details> ## What changed - `getSourceAsKey` now drops the git ref from GitHub raw URLs, so `main/…`, `refs/heads/main/…` and a pinned SHA all resolve to the same list identity (`owner/repo/path`). Only refs that can be delimited with certainty are stripped; a ref that may itself contain slashes is left verbatim rather than guessed at. It is the single key builder behind `blockedCountriesPerList` / `consentHashPerList`, so every consumer picks this up: `useIsListBlocked`, `useFilterBlockedLists`, `useRestrictedListInfo`, `useIsListRequiresConsent`, `useFilterListsWithConsent`, `useConsentAwareToggleList`, `allTokensAtom`, `BlockedListSourcesUpdater`. - `listsStatesMapAtom` drops re-pinned leftovers on read: when several stored sources share that key, only the URL the app currently ships is surfaced. Virtual widget lists are merged in afterwards and never touched. - `upsertListsAtom` applies the same filter to what it persists, so the leftovers are removed from IndexedDB rather than just hidden. Its `listsStates` argument is what the app just fetched for that chain, which is exactly the "currently shipped" set needed to tell a leftover from a live list. ## Why RWA geoblocking is matched on the exact token list URL, but those URLs are pinned commit SHAs that change every time an issuer adds a ticker. This release re-pinned four of them: ``` - .../ondoprotocol/cowswap-global-markets-token-list/refs/heads/main/tokenlist.json + .../ondoprotocol/cowswap-global-markets-token-list/cf97552d.../tokenlist.json ``` Stored lists are keyed by URL and nothing prunes a URL that drops out of the default config, so a returning user keeps the branch-ref entry forever. The CMS moved to the pinned URL, the stored key did not, `blockedCountriesPerList[storedKey]` came back `undefined`, and `useFilterBlockedLists` keeps a list it has no entry for. Result on staging: RWA lists stayed visible from a restricted location, while incognito looked correct because it had no stored state. Two problems, hence two layers: - Without the ref-agnostic key this recurs silently on every re-pin — the UI looks fine and geoblocking is just off. - The leftovers are not free. Each one keeps a full parsed token list: the xStocks list is **1.26 MB** and is configured on chains 1, 56, 42161 and 57073; Ondo is **334 KB** on chains 1 and 56. `listsStatesByChainAtom` loads all of it into memory with `getOnInit` and re-serializes it on every upsert, so a leftover costs disk, heap and stringify work on every write until it is pruned. ## QA Testing Needs the `isRwaGeoblockEnabled` flag on, and a VPN for the restricted-location cases (BNB below; `PL`, `DE`, `US` are all in `RESTRICTED_COUNTRIES`). The bug only reproduces with list state stored *before* the re-pin, so a clean preview profile cannot show it. Seed the origin under test first, using either recipe. **The fixture is consumed by the fix.** Once the preview loads and finishes fetching lists, the leftover is pruned from storage for good. Re-seed before every run, and do the staging control step on its own seed. **Recipe A — copy an existing profile's state onto the preview** The full `allTokenListsInfoAtom:v7` value is megabytes — every list with all its tokens — so do not move it wholesale. Only the GitHub-raw list entries matter here, and only their identity, not their tokens. 1. Open DevTools on an origin that still holds the old lists (https://staging.swap.cow.fi or https://swap.cow.fi) and run this. It keeps just the re-pinnable entries and empties their token arrays, which cuts the payload to a few hundred characters: ```js await (async () => { const db = await new Promise((ok, err) => { const req = indexedDB.open('cowswap_jotai') req.onsuccess = () => ok(req.result) req.onerror = () => err(req.error) }) const store = db.transaction('keyvaluepairs', 'readonly').objectStore('keyvaluepairs') const raw = await new Promise((ok) => { const req = store.get('allTokenListsInfoAtom:v7') req.onsuccess = () => ok(req.result) }) const slim = {} for (const [chainId, chain] of Object.entries(JSON.parse(raw))) { for (const [source, state] of Object.entries(chain)) { if (state === 'deleted' || !source.includes('raw.githubusercontent.com')) continue slim[chainId] ??= {} // tokens play no part in list hiding and are what makes the dump unmanageable slim[chainId][source] = { ...state, list: { ...state.list, tokens: [] } } } } const payload = JSON.stringify(slim) console.log(`${payload.length} chars, select the next line and copy it:`) console.log(payload) })() ``` Copy the logged line by selecting it. Do not use `copy()` — it is silently a no-op in Firefox on macOS. 2. Open https://swap-dev-git-fix-rwa-list-migration-cowswap-dev.vercel.app **once and let it load** — localforage only creates the `cowswap_jotai` database and its `keyvaluepairs` store on first app start, and the import throws `NotFoundError` without it. 3. In the preview's console, paste the copied JSON where marked and run. This merges the entries in; the preview's own lists are left alone: ```js const STALE = /* paste the copied JSON here */ await (async () => { const KEY = 'allTokenListsInfoAtom:v7' const db = await new Promise((ok, err) => { const req = indexedDB.open('cowswap_jotai') req.onsuccess = () => ok(req.result) req.onerror = () => err(req.error) }) const read = db.transaction('keyvaluepairs', 'readonly').objectStore('keyvaluepairs') const raw = await new Promise((ok) => { const req = read.get(KEY) req.onsuccess = () => ok(req.result) }) const data = raw ? JSON.parse(raw) : {} let merged = 0 for (const [chainId, chain] of Object.entries(STALE)) { data[chainId] ??= {} for (const [source, state] of Object.entries(chain)) { if (data[chainId][source]) continue data[chainId][source] = state merged++ } } db.transaction('keyvaluepairs', 'readwrite').objectStore('keyvaluepairs').put(JSON.stringify(data), KEY) console.log(`merged ${merged} entries, reload now`) })() ``` 4. Reload. **Recipe B — synthesize the pre-re-pin state (no second profile needed)** Open the origin once, then run this and reload. It clones every SHA-pinned list into its old `refs/heads/main` URL, which is exactly the state a returning user has: ```js await (async () => { const KEY = 'allTokenListsInfoAtom:v7' const db = await new Promise((ok, err) => { const req = indexedDB.open('cowswap_jotai') req.onsuccess = () => ok(req.result) req.onerror = () => err(req.error) }) const read = db.transaction('keyvaluepairs', 'readonly').objectStore('keyvaluepairs') const raw = await new Promise((ok) => { const req = read.get(KEY) req.onsuccess = () => ok(req.result) }) const data = JSON.parse(raw) let seeded = 0 for (const chain of Object.values(data)) { for (const [source, state] of Object.entries(chain)) { const stale = source.replace( /^(https:\/\/raw\.githubusercontent\.com\/[^/]+\/[^/]+\/)[0-9a-f]{40}\//, '$1refs/heads/main/', ) if (stale === source || chain[stale]) continue chain[stale] = { ...state, source: stale } seeded++ } } db.transaction('keyvaluepairs', 'readwrite').objectStore('keyvaluepairs').put(JSON.stringify(data), KEY) console.log(`seeded ${seeded} pre-re-pin entries`) })() ``` Preview URL QA (run after seeding, on https://swap-dev-git-fix-rwa-list-migration-cowswap-dev.vercel.app/#/56/swap/USDC/WBNB): - Control first, on its own seed: seed https://staging.swap.cow.fi and open Manage → Lists from a restricted location. Ondo, xStocks and Reserve Restricted DTFs BNB are listed, each twice — once with its real token count and once with the seeded `0 tokens`. That is the bug. - Seed the preview and repeat from a restricted location: all three must be absent. - Seed the preview again and open it from a non-restricted location: each of the three appears **exactly once**, with its real token count. The seeded `0 tokens` row must not be there. - Storage is cleaned, not just hidden. After the page has finished loading lists, run this and expect `0`: ```js await (async () => { const db = await new Promise((ok, err) => { const req = indexedDB.open('cowswap_jotai') req.onsuccess = () => ok(req.result) req.onerror = () => err(req.error) }) const store = db.transaction('keyvaluepairs', 'readonly').objectStore('keyvaluepairs') const raw = await new Promise((ok) => { const req = store.get('allTokenListsInfoAtom:v7') req.onsuccess = () => ok(req.result) }) const leftovers = Object.values(JSON.parse(raw)).flatMap((chain) => Object.keys(chain).filter((source) => source.includes('/refs/heads/')), ) console.log(`${leftovers.length} leftovers still stored`, leftovers) })() ``` - Regression: with the flag off, or from a non-restricted location, all other lists (CoW Swap, CoinGecko, Uniswap) behave as before, and adding, toggling and deleting a custom list still works. Developer verification: - `libs/tokens/src/hooks/lists/useIsListBlocked.test.tsx` pins the key normalization: `main`, `master`, `refs/heads/` and SHA forms collapse to one key; nested paths survive; non-GitHub sources are untouched; a ref that cannot be delimited is returned verbatim; two different files in the same repo never merge. - `libs/tokens/src/state/tokenLists/tokenListsStateAtom.test.ts` adds three cases: `listsStatesMapAtom` surfaces only the shipped URL and leaves unrelated lists alone, keeps one entry when no stored URL is the shipped one, and `upsertListsAtom` removes the leftover from `listsStatesByChainAtom` itself rather than only from the rendered map. Each fails with its filter stubbed out. The first derives its fixture from `DEFAULT_TOKENS_LISTS`, so it will not rot on the next re-pin. - `npx jest --config libs/tokens/jest.config.ts` — 9 suites, 61 tests. Reviewer note: - This deliberately does not clean storage with an IndexedDB migration. `listsStatesByChainAtom` loads with `getOnInit` and is re-persisted wholesale by `upsertLists`, so a write made directly to localForage races the atom's initial read and is clobbered by the in-memory copy. Both filters therefore go through jotai. - The two layers cover different cases and neither is redundant: the read filter is correct immediately, including before the first fetch lands and on chains that are never upserted; the write filter is what actually reclaims the storage. - CodeRabbit flagged that a branch or tag name containing `/` normalizes to the wrong key ([review comment](#8089 (comment))). Confirmed, and the consequence is worse than a missed match: `refs/heads/release/v1/tokenlist.json` and `refs/heads/main/v1/tokenlist.json` both produced `…/acme/repo/v1/tokenlist.json`, so `dropRepinnedDuplicates` would have deleted a list that is not a duplicate. Fixed by stripping only refs that can be identified with certainty — a 40-character SHA, or `main`/`master` in short or `refs/heads/` form. Anything else is left verbatim, which costs a missed match and nothing else. Both cases are pinned by tests. - Unchanged by this PR: a per-list enabled/disabled preference still does not survive a re-pin, because `upsertListsAtom` carries `isEnabled` over by exact URL. Worth a follow-up, not bundled here. </details> # Self-checks - [x] I have read [CONTRIBUTING.md](../CONTRIBUTING.md) - [x] I have manually tested changes on Vercel preview deployment - [x] I have done self-review and (or) AI review - [x] I have addressed all comments from @coderabbitai - [x] I have less than three open PRs/Stacks in this repo at the moment of creating this PR
## What changed - Keep page scroll locking on `<body>`. - Stop turning `<html>` into an overflow container while the page is locked. Demo: https://github.com/user-attachments/assets/6608ce09-2d31-40b2-abb4-e39467e39cf6 ## Why - Locking `<html>` after the page had been scrolled displaced the sticky mobile header, hiding the close control and first menu items. ## QA Testing Preview URL QA: - Open the [swap-dev preview](https://swap-dev-git-hotfix-fix-mobile-menu-header-cowswap-dev.vercel.app) at `375 × 667` or on a mobile device. - Scroll down, open the header menu, and confirm the header, close control, and `Trade` item remain fully visible. - Close the menu, then confirm normal page scrolling is restored. - Open a token selector and confirm the background page remains locked while it is open. - Close the token selector and confirm normal page scrolling is restored. ## Preview URLs | Surface | URL | | --- | --- | | swap-dev - branch preview URL | https://swap-dev-git-hotfix-fix-mobile-menu-header-cowswap-dev.vercel.app | | explorer-dev - branch preview URL | https://explorer-dev-git-hotfix-fix-mobile-menu-header-cowswap-dev.vercel.app | | cowfi - branch preview URL | https://cowfi-git-hotfix-fix-mobile-menu-header-cowswap.vercel.app | | storybook - branch preview URL | https://storybook-git-hotfix-fix-mobile-menu-header-cowswap-dev.vercel.app | | sdk-tools - branch preview URL | https://sdk-tools-git-hotfix-fix-mobile-menu-header-cowswap-dev.vercel.app | | widget-configurator - branch preview URL | https://widget-configurator-git-hotfix-fix-mobile-me-6f8d0e-cowswap-dev.vercel.app | <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Bug Fixes** - Adjusted scroll-lock behavior so modal and overlay states continue locking the page through the `<body>` element without additionally locking the `<html>` element. - Improved scrolling behavior in scenarios where the page uses the `noScroll` state, reducing unintended scroll-container restrictions. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and publish to npm yourself or [setup this action to publish automatically](https://github.com/changesets/action#with-publishing). If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases > The changelog information of each package has been omitted from this message, as the content exceeds the size limit. ## @cowprotocol/cow-fi@2.13.1 ## @cowprotocol/cowswap@3.25.1 ## @cowprotocol/explorer@4.11.1 ## @cowprotocol/widget-configurator@3.12.1 ## @cowprotocol/balances-and-allowances@3.12.1 ## @cowprotocol/multicall@3.6.1 ## @cowprotocol/snackbars@2.3.1 ## @cowprotocol/tokens@3.11.1 ## @cowprotocol/ui@3.12.1 ## @cowprotocol/wallet@3.12.1 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
cowswap-release-sync
Bot
force-pushed
the
automation/sync-main-to-develop
branch
from
September 7, 2026 10:36
01d8391 to
4914e04
Compare
azebuado
approved these changes
Sep 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains an automated merge commit from
mainintodevelop.