Skip to content

build(deps): DEP-10 — styled-components 6 (WALLET-1337)#1387

Merged
Comp0te merged 21 commits into
release/2.6.0from
WALLET-1337-dep-10-styled-components-5-to-6
Jul 3, 2026
Merged

build(deps): DEP-10 — styled-components 6 (WALLET-1337)#1387
Comp0te merged 21 commits into
release/2.6.0from
WALLET-1337-dep-10-styled-components-5-to-6

Conversation

@ost-ptk

@ost-ptk ost-ptk commented Jul 2, 2026

Copy link
Copy Markdown
Member

Summary

Upgrades styled-components 5.3.11 → 6.4.3. v6 removes automatic DOM-prop filtering (v5 silently dropped non-HTML custom props like color, active, loading before they reached the DOM; v6 forwards everything, triggering React warnings and invalid DOM attributes).

  • Restores v5 semantics globally via a new CspStyleSheetManager wrapper (src/libs/ui/style-sheet-manager.tsx, using @emotion/is-prop-valid), mounted in all 5 UI app entry points, instead of renaming ~313 custom-prop usages across ~140 files.
  • Fixes v6 type/API breaks across the UI library: typography.tsx, svg-icon.tsx (both had local withConfig({shouldForwardProp}) that needed the v6 signature update — see note below), list.tsx (removed FlattenInterpolation/ThemeProps types), global-style.ts, containers.ts, avatar.tsx (assorted v6 type-inference tightenings, discovered via a full tsc --noEmit pass, not caught by the original grep-based audit).
  • Real bug found via manual browser QA, not caught by tsc/lint/tests: styled-components v6 resolves shouldForwardProp as componentLevel || contextLevel — a component's own .withConfig({shouldForwardProp}) fully replaces (does not compose with) the global StyleSheetManager filter. The 3 withConfig sites in this codebase each only excluded their one local prop, so every other custom prop on those 3 components was still leaking to the DOM. Fixed by composing isPropValid into all 3 local callbacks.

Scope note

This ticket originally also planned to replace 'unsafe-inline' with a per-build CSP nonce in style-src (a separate security-hardening item). That work was implemented, then reverted after manual QA found @lottiefiles/react-lottie-player injects inline <style> tags with zero nonce support (no config hook, vendored style-inject utility) — under a nonce-only style-src this broke rendering. CSP-nonce hardening is deferred to a follow-up ticket, pending a decision on the lottie-player situation (CSP hash for its static injected CSS vs. swapping to lottie-web directly). style-src stays 'unsafe-inline', unchanged from before this PR. The CspStyleSheetManager wrapper this PR adds needs no rework when that follow-up lands — it already passes process.env.CSP_NONCE through as a harmless no-op.

Jira: https://make-software.atlassian.net/browse/WALLET-1337

Test plan

  • npm run ci-check (format:check + lint + tsc + test) — all green
  • npm run build:chrome / build:firefox — both succeed; verified generated CSP unchanged ('unsafe-inline', no nonce)
  • npm run build:safari — webpack/JS compile phase succeeds; Xcode PACK step fails on a pre-existing, unrelated issue (stale hardcoded chunk filenames in project.pbxproj, tracked separately, coordinated with DEP-11)
  • Manual QA in Chrome (popup, light/dark) — zero "React does not recognize the X prop" warnings, zero CSP violations, confirmed after fixing the shouldForwardProp composition bug
  • extension-manifest-reviewer subagent — 0 findings; confirmed the CSP-nonce introduction+revert is a byte-exact no-op on webpack.config.js/src/utils.ts, all 3 manifest JSON files untouched
  • Final whole-branch code review — 0 Critical/Important findings, 1 Minor (addressed)

ost-ptk and others added 19 commits July 2, 2026 09:49
…a 1.5 (WALLET-1333)

Bump redux 4.2.1 -> 5.0.1, react-redux 8.1.3 -> 9.3.0, reselect 4.1.7 -> 5.2.0,
and redux-saga 1.2.3 -> 1.5.0.

Fix a dead deep-import (`react-redux/es/exports`) left over from react-redux 8,
which no longer resolves under react-redux 9's ESM `exports` map.

Wrap four previously-unmemoized selectors (selectVaultAccountsPublicKeys,
selectVaultAccountsExceptLedgersAccounts, selectAllContactsNames,
selectAllContactsPublicKeys) in createSelector, and split
selectIsAccountConnected's tuple-returning input selector into two scalar
input selectors — both were returning a new reference on every call, which
reselect 5's new default inputStabilityCheck/react-redux 9's stabilityCheck
now surface as dev-mode warnings.
…react-hook-form 7.80 (WALLET-1335)

Atomic bump of 4 coupled packages: @hookform/resolvers@5 is typed against
yup@1 and peers on react-hook-form@^7.55, so all three must move together;
react-router-dom@7 tags along since it needs the same Node 20 baseline.

Mechanical fallout fixed: 12 deep imports of
@hookform/resolvers/yup/dist/yup -> @hookform/resolvers/yup (v5 dist
layout changed), 2 deep imports of yup/es/types -> yup (v1 dropped es/),
and use-typed-location.ts's Location import moved from the now-removed
@remix-run/router package to react-router-dom's re-export.

Type reconciliation for yup 1's stricter inference (yupResolver vs
FormValues) is deliberately out of scope here and follows in later
commits.
…-cspr forms (WALLET-1335)

yup 1 infers Yup.string() without .required()/.defined() as an
optional/undefinable output, which broke yupResolver's strict typing
against useForm<FormValues>() wherever the *FormValues interface
declared the field as a plain required string.

- form-validation-rules.ts: add .required() to useCreatePasswordRule,
  useVerifyPasswordAgainstHashRule, useRepeatPasswordRule, and
  useValidSecretPhraseRule — all four back genuinely required fields
  (password, confirm password, secret phrase), so this is also a type
  fix and a validation-completeness fix.
- buy-cspr.ts: casperAmount is an unvalidated derived/display field
  (set via setValue, never user-required), so relax
  BuyCSPRFormValues.casperAmount to string | undefined instead of
  forcing .required(). yup's inferred schema type is structurally
  asymmetric between Input and Output for non-required fields, so no
  single FormValues shape satisfies Resolver<F, any, F> here; cast the
  resolver to Resolver<BuyCSPRFormValues> to bridge that gap.
…on schema (WALLET-1335)

useTransferAmountForm picks between two differently-shaped yup schemas
(erc20AmountFormSchema has paymentAmount, csprAmountFormSchema doesn't),
making amountFormSchema's type a TS union. keyof of a union is the
intersection of keys, so the resolver's inferred `names` type silently
dropped paymentAmount, breaking assignability against
Resolver<TransferAmountFormValues, ...>.

Add an unvalidated paymentAmount field to csprAmountFormSchema so both
union branches share the same key set (paymentAmount stays ERC-20-only
in practice — this is purely to align the inferred type, not a
validation change for CSPR transfers). That alone doesn't fully close
the gap because of the same Input/Output asymmetry yup produces for
unvalidated fields (as in buy-cspr.ts), so cast the resolver to
Resolver<TransferAmountFormValues> as well.
…gSchema cleanup (WALLET-1335)

Yup.mixed() with no generic now infers its .test() callback argument
as yup 1's narrow AnyPresentValue type, breaking .length/index access
on secretKeyFile's file-type validators. Give it an explicit
Yup.mixed<FileList>() — the field is bound to a native
<input type="file"> and was always a FileList at runtime, so also fix
ImportAccountFormValues.secretKeyFile from the pre-existing (wrong)
string | undefined to FileList | undefined. Same Input/Output
asymmetry as buy-cspr.ts/transfer.ts requires the same
yupResolver(...) as Resolver<FormValues> cast.

Also fix an unrelated pre-existing type bug surfaced during review:
unlock-vault/index.tsx and password-protection-page/index.tsx typed
their Worker message payload's isPasswordCorrect as
Yup.StringSchema<...> instead of boolean, which is what
verify-password-worker.ts actually posts back
(verifyPasswordAgainstHash returns Promise<boolean>). The wrong type
never caused a runtime issue (TS can't check cross-Worker message
shapes, and both call sites only did truthy/falsy checks) but was
worth correcting while touching yup types in this area.

This closes out the type-reconciliation phase of the yup 1 /
resolvers 5 / react-hook-form 7.80 bump — npm run tsc is now clean
project-wide.
…w-flow missing awaits (WALLET-1335)

rename-account.spec.ts: react-router 7 no longer unmounts the outgoing
route synchronously with the incoming one (v6 did), so after
navigate() there can be a brief window (confirmed via reproduction:
under ~10ms) where the account-settings page's stale heading and the
Home header banner both render the same account name. The
unscoped getByText(name) assertion hit a Playwright strict-mode
violation deterministically. Scope both assertions to the header
banner, which is what the test actually intends to verify post-close.

review-flow.spec.ts: 8 popupExpect(...).toBeVisible() calls across all
three tests were missing await (pre-existing since #1151, unrelated
to this bump), so the test function could return before the assertion
resolved. Add the missing awaits.
…x-saga-1-5' into WALLET-1335-dep-8-router-forms-react-router-7-yup-1-resolvers-5-react-hook-form-7-80
…dep-8-router-forms-react-router-7-yup-1-resolvers-5-react-hook-form-7-80

# Conflicts:
#	package-lock.json
#	package.json
…xt 17, @formatjs/intl 4) (WALLET-1334)

Bumps i18next-http-backend 3.0.5 -> 4.0.0 (finishes the DEP-2 security
fix, drops the bundled cross-fetch since MV3 service workers have
native fetch), i18next 23 -> 26, react-i18next 14 -> 17,
i18next-browser-languagedetector 7 -> 8, @formatjs/intl 2 -> 4
(pure ESM), and the i18next-conv dev tool 15 -> 17.
i18next-parser stays on hold as an independent extractor.

Extends jest.config.js transformIgnorePatterns to transpile
@formatjs/intl v4's ESM dependency tree, including the unscoped
intl-messageformat transitive that isn't covered by the @formatjs
scope alone.

Converts the two locale dev scripts to ESM to fix a latent
ERR_REQUIRE_ESM from i18next-conv already being ESM-only.

No changes needed in src/libs/i18n/i18n.ts or language-detector.ts —
the runtime config and custom detector shape are forward-compatible.
styled-components v6 resolves shouldForwardProp as componentLevel ||
contextLevel: a component's own .withConfig({shouldForwardProp}) fully
replaces the StyleSheetManager-provided filter rather than composing with
it. typography.tsx (StyledTypography, StyledHeader) and svg-icon.tsx
(Container) each define a local shouldForwardProp that only excludes their
one component-specific prop, so every other custom prop (color, active,
wordBreak, ellipsis, isDarkMode, displayContext, ...) was forwarded
straight to the DOM, unfiltered by the global CspStyleSheetManager filter.
Confirmed via manual browser QA (React "does not recognize the X prop"
warnings + invalid DOM attributes) and by reading styled-components
6.4.3's compiled resolution logic directly.
Reverts commit 5aa885c. Manual QA surfaced two libraries that inject
inline <style> tags with no nonce awareness under the new nonce-only
style-src: style-loader's runtime (used for the mac-scrollbar and
react-loading-skeleton CSS imports — fixable later via __webpack_nonce__,
which style-loader does support) and @lottiefiles/react-lottie-player's
vendored style-inject utility (no nonce support at all, would need either
a CSP hash for its static injected CSS or a library swap).

The StyleSheetManager wiring this ticket already built (CspStyleSheetManager
mounted in all 5 apps) stays as-is and needs no rework when the nonce
work resumes — it already passes process.env.CSP_NONCE through as a
no-op until that env var is defined again. style-src reverts to
'unsafe-inline' for now; P1.5(a) tracked as a separate follow-up once
the lottie-player situation is resolved.
Base automatically changed from WALLET-1334-dep-9-i-18-n-formatting-stack to release/2.6.0 July 2, 2026 20:16
…dep-10-styled-components-5-to-6

# Conflicts:
#	package-lock.json
#	package.json
@ost-ptk ost-ptk marked this pull request as ready for review July 3, 2026 07:25
@ost-ptk ost-ptk requested a review from Comp0te July 3, 2026 07:25
':hover'/':active'/':focus' object keys (with or without a leading space)
compile to a descendant-combinator selector (`.btn :hover`, matching any
hovered descendant) rather than the button's own state (`.btn:hover`) —
CSS object-key syntax requires an explicit '&' prefix to target the host
element itself. Pre-existing since 2022 (commit a831936), unrelated to
the styled-components v6 bump (button.tsx had zero diff before this fix),
but found and fixed during this ticket's manual QA: hovering the Send/More
icons (descendants of the button) was applying the button's intended
hover/active background to the icon itself instead of the button, and
the button's focus outline was never actually suppressed.

@Comp0te Comp0te left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@Comp0te Comp0te merged commit 7dd5a44 into release/2.6.0 Jul 3, 2026
1 check passed
@Comp0te Comp0te deleted the WALLET-1337-dep-10-styled-components-5-to-6 branch July 3, 2026 12:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants