Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions scripts/ds-lint-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"rawHex": 65,
"rawHexFiles": 41,
"inlineStyle": 50,
"stockTextSize": 362,
"stockTextSize": 361,
"dsTextScale": 10,
"nonDsClassesInViews": 138,
"useSearchParamsFiles": 47,
Expand All @@ -11,5 +11,10 @@
"offRampPalette": 0,
"deadLegacyTokens": 0,
"consumedUndefinedTokens": 0,
"classNameSitesInPages": 314
"classNameSitesInPages": 314,
"fontWeightOnTypeToken": 13,
"offScaleSpacing": 181,
"iconOffScale": 64,
"offScaleRadius": 33,
"rawDuration": 9
}
52 changes: 52 additions & 0 deletions scripts/ds-lint-counts.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,53 @@ counts.classNameSitesInPages = files
.filter((f) => /^app\/\(mobile-ui\)\//.test(f.path) && /(^|\/)page\.tsx$/.test(f.path) && !f.path.includes('/dev/'))
.reduce((sum, f) => sum + countMatches(f.text, /className=/g), 0)

// composition-drift metrics (2026-09-01 sweep). design.md laws the token
// metrics above cannot see: stacked weights mint off-ramp type styles, the
// spacing/radius/motion scales ban off-scale values, icons have three sizes.
// deliberate holds (geometry-driven indents like Notification's pl-7, boards
// pending a ruling) live inside the baseline, not an allowlist — a ruling
// drives the count down, new drift pushes it up and fails.
const WEIGHT_STACK_RE = /\bfont-(?:bold|semibold|extrabold)\b/
const TYPE_TOKEN_RE = /\btext-(?:body|heading|label|button)-[a-z-]+\b/
counts.fontWeightOnTypeToken = files
.filter((f) => isTsx(f) && !allowed(f.path))
Comment thread
innolope-dev marked this conversation as resolved.
Outdated
.reduce(
(sum, f) => sum + f.text.split('\n').filter((l) => TYPE_TOKEN_RE.test(l) && WEIGHT_STACK_RE.test(l)).length,
Comment thread
innolope-dev marked this conversation as resolved.
Outdated
0
)
// allowlist inversion, not a blocklist: tailwind v4 compiles ANY numeric step
// (p-4.5, pr-18, -mt-5), so the metric flags every numeric spacing utility —
// negative forms and variant prefixes included — whose magnitude is not a
// documented scale step, instead of enumerating known-bad values.
const SPACING_STEPS = new Set(['0', '0.5', '1', '2', '3', '4', '6', '8', '10', '12', '14', '16'])
const NUMERIC_SPACING_RE =
/(?<![a-z0-9-])-?(?:px|py|pt|pb|pl|pr|p|mx|my|mt|mb|ml|mr|m|gap-x|gap-y|gap|space-y|space-x)-([0-9]+(?:\.[0-9]+)?)(?![0-9.a-z%\]])/g
Comment thread
innolope-dev marked this conversation as resolved.
Outdated
counts.offScaleSpacing = files
.filter((f) => isTsx(f) && !allowed(f.path))
.reduce((sum, f) => {
let n = 0
for (const m of f.text.matchAll(NUMERIC_SPACING_RE)) if (!SPACING_STEPS.has(m[1])) n++
return sum + n
}, 0)
// three ways an Icon gets sized: the size prop (also width/height, which the
// component forwards), Button's iconSize, and — banned outright by the icon
// law — tailwind size-/h-/w- classes on the Icon itself.
const OFF_SCALE_ICON_RE =
/<Icon\s[^>]*?\b(?:size|width|height)=\{(?!16\}|20\}|24\})[0-9]+\}|\biconSize=\{(?!16\}|20\}|24\})[0-9]+\}|<Icon\s[^>]*?className="[^"]*\b(?:size|h|w)-[0-9]/g
counts.iconOffScale = files
.filter((f) => isTsx(f) && !allowed(f.path))
.reduce((sum, f) => sum + countMatches(f.text, OFF_SCALE_ICON_RE), 0)
const OFF_SCALE_RADIUS_RE = /\brounded(?:-[trbl]{1,2})?-(?:md|lg|xl|2xl|3xl)\b/g
counts.offScaleRadius = files
.filter((f) => isTsx(f) && !allowed(f.path))
.reduce((sum, f) => sum + countMatches(f.text, OFF_SCALE_RADIUS_RE), 0)
// any numeric duration is off-token (the motion scale is instant/fast/
// moderate/slow); arbitrary values (duration-[250ms]) count too.
const RAW_DURATION_RE = /\bduration-(?:[0-9]+\b|\[[^\]]+\])/g
counts.rawDuration = files
.filter((f) => isTsx(f) && !allowed(f.path))
.reduce((sum, f) => sum + countMatches(f.text, RAW_DURATION_RE), 0)

// dsTextScale and nuqsFiles are adoption counts (should go UP) — everything
// else is debt (must only go DOWN). the ratchet only enforces the debt keys.
const DEBT_KEYS = [
Expand All @@ -266,6 +313,11 @@ const DEBT_KEYS = [
'classNameSitesInPages',
'deadLegacyTokens',
Comment thread
innolope-dev marked this conversation as resolved.
'consumedUndefinedTokens',
'fontWeightOnTypeToken',
'offScaleSpacing',
'iconOffScale',
'offScaleRadius',
'rawDuration',
]

const mode = process.argv[2] ?? ''
Expand Down
2 changes: 1 addition & 1 deletion src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@

useEffect(() => {
fetchUser()
}, [])

Check warning on line 185 in src/app/(mobile-ui)/add-money/[country]/bank/page.tsx

View workflow job for this annotation

GitHub Actions / eslint

React Hook useEffect has a missing dependency: 'fetchUser'. Either include it or remove the dependency array

const peanutWalletBalance = useMemo(() => {
return balance !== undefined ? formatAmount(formatUnits(balance, PEANUT_WALLET_TOKEN_DECIMALS)) : ''
Expand Down Expand Up @@ -476,7 +476,7 @@
<div className="space-y-8 flex flex-col justify-start">
<NavHeader title={t('title')} onPrev={onBack} />
<div className="my-auto flex flex-grow flex-col justify-center gap-4 md:my-0">
<div className="text-body-s font-bold">{t('howMuchToAdd')}</div>
<div className="text-label-l">{t('howMuchToAdd')}</div>
<AmountInput
initialAmount={rawTokenAmount}
setPrimaryAmount={handleTokenAmountChange}
Expand Down
6 changes: 3 additions & 3 deletions src/app/(mobile-ui)/profile/backup/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export default function BackupPage() {

<Section title={t('enableNow')}>
<Card>
<ol className="space-y-4 list-decimal py-2 pl-5">
<ol className="space-y-4 list-decimal py-2 pl-6">
{backupSteps.map((step, index) => (
<li key={index}>
<p className="font-bold text-foreground-primary">{step.title}</p>
Expand Down Expand Up @@ -104,7 +104,7 @@ export default function BackupPage() {
titleClassName="text-heading-xs"
content={
<div className="space-y-3 w-full">
<ol className="list-decimal pl-5 text-left text-body-s text-foreground-primary">
<ol className="list-decimal pl-6 text-left text-body-s text-foreground-primary">
<li>{t('changePhoneModal.step1')}</li>
<li>{t('changePhoneModal.step2', { platform })}</li>
<li>{t('changePhoneModal.step3')}</li>
Expand Down Expand Up @@ -135,7 +135,7 @@ export default function BackupPage() {
<p className="mt-1 text-body-s text-foreground-primary">
{t('exportKeysModal.saferIntro')}
</p>
<ul className="space-y-1 mt-2 list-disc pl-5 text-body-s text-foreground-primary">
<ul className="space-y-1 mt-2 list-disc pl-6 text-body-s text-foreground-primary">
<li>{t('exportKeysModal.bullets.screenshot')}</li>
<li>{t('exportKeysModal.bullets.textMessage')}</li>
<li>{t('exportKeysModal.bullets.noteApp')}</li>
Expand Down
6 changes: 3 additions & 3 deletions src/app/(mobile-ui)/qr-pay/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1454,7 +1454,7 @@ export default function QRPayPage() {
<PointsCard points={pointsData.estimatedPoints} pointsDivRef={pointsDivRef} />
)}

<div className="space-y-5 w-full">
<div className="space-y-4 w-full">
{/* Show Claim Reward button if eligible and not claimed yet */}
{rewardClaimable ? (
<Button
Expand Down Expand Up @@ -1486,7 +1486,7 @@ export default function QRPayPage() {
>
{/* progress fill from left to right */}
<div
className="absolute inset-0 bg-black transition-all duration-100"
className="absolute inset-0 bg-black transition-all duration-instant"
style={{
width: `${holdProgress}%`,
left: 0,
Expand All @@ -1498,7 +1498,7 @@ export default function QRPayPage() {
<>
<span className="relative z-10">{label}</span>
<span
className="absolute inset-0 z-20 flex items-center justify-center text-white transition-all duration-75"
className="absolute inset-0 z-20 flex items-center justify-center text-white transition-all duration-instant"
style={{ clipPath: `inset(0 ${100 - holdProgress}% 0 0)` }}
>
{label}
Expand Down
2 changes: 1 addition & 1 deletion src/app/(mobile-ui)/qr/[code]/success/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export default function RedirectQrSuccessPage() {
<div className="flex gap-3">
<Icon name="star" size={20} className="flex-shrink-0 text-action-secondary" />
<div className="space-y-1">
<p className="text-body-s font-bold">{t('claimSuccess.putItAnywhere')}</p>
<p className="text-label-l">{t('claimSuccess.putItAnywhere')}</p>
<p className="text-body-xs text-foreground-secondary">
{t('claimSuccess.stickerDescription')}
</p>
Expand Down
4 changes: 2 additions & 2 deletions src/app/(mobile-ui)/rewards/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
useEffect(() => {
// re-fetch user to get the latest invitees list for showing heart icon
fetchUser()
}, [])

Check warning on line 106 in src/app/(mobile-ui)/rewards/page.tsx

View workflow job for this annotation

GitHub Actions / eslint

React Hook useEffect has a missing dependency: 'fetchUser'. Either include it or remove the dependency array

// isPending, not isLoading: both queries wait on `user`, and a disabled query
// reports isLoading false. isLoading would send the first paint to the error
Expand Down Expand Up @@ -206,7 +206,7 @@
/>
<div className="relative h-1 flex-1 overflow-hidden rounded-full bg-background-disabled">
<div
className="h-full rounded-full bg-gradient-to-r from-action-primary to-action-primary-hover transition-all duration-500"
className="h-full rounded-full bg-gradient-to-r from-action-primary to-action-primary-hover transition-all duration-slow"
style={{
width: `${
tierInfo?.data.currentTier >= 2
Expand Down Expand Up @@ -284,7 +284,7 @@
onClick={() => router.push(profileUrl(user.invitedBy!))}
className="inline-flex cursor-pointer items-center gap-1 font-bold"
>
{user.invitedBy} <Icon name="invite-heart" size={14} />
{user.invitedBy} <Icon name="invite-heart" size={16} />
</span>{' '}
{t('invitedYou')}{' '}
</>
Expand Down
2 changes: 1 addition & 1 deletion src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@

useEffect(() => {
fetchUser()
}, [])

Check warning on line 412 in src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx

View workflow job for this annotation

GitHub Actions / eslint

React Hook useEffect has a missing dependency: 'fetchUser'. Either include it or remove the dependency array

// Balance validation
useEffect(() => {
Expand Down Expand Up @@ -454,7 +454,7 @@
/>

{view === 'INITIAL' && (
<div className="my-auto space-y-4 flex h-full w-full flex-col justify-center pb-5">
<div className="my-auto space-y-4 flex h-full w-full flex-col justify-center pb-4">
<PeanutActionDetailsCard
countryCodeForFlag={countryCodeForFlag()}
avatarSize="small"
Expand Down
2 changes: 1 addition & 1 deletion src/app/(mobile-ui)/withdraw/crypto/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ export default function WithdrawCryptoPage() {
}

return (
<div className="mx-auto space-y-4 min-h-[inherit] w-full max-w-md self-center">
<div className="mx-auto flex min-h-[inherit] w-full max-w-md flex-col gap-4 self-center">
{currentView === 'INITIAL' && (
<InitialWithdrawView
amount={usdAmount}
Expand Down
2 changes: 1 addition & 1 deletion src/app/(mobile-ui)/withdraw/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@
if (amountFromContext) {
setShowAllWithdrawMethods(true)
}
}, [])

Check warning on line 204 in src/app/(mobile-ui)/withdraw/page.tsx

View workflow job for this annotation

GitHub Actions / eslint

React Hook useEffect has missing dependencies: 'amountFromContext' and 'setShowAllWithdrawMethods'. Either include them or remove the dependency array

const validateAmount = useCallback(
(amountStr: string): boolean => {
Expand Down Expand Up @@ -245,7 +245,7 @@
setError({ showError: true, errorMessage: message })
return false
},
[balance, maxDecimalAmount, setError, selectedTokenData?.price, isFromSendFlow, minUsdAmount, t, tErrors]

Check warning on line 248 in src/app/(mobile-ui)/withdraw/page.tsx

View workflow job for this annotation

GitHub Actions / eslint

React Hook useCallback has an unnecessary dependency: 'selectedTokenData.price'. Either exclude it or remove the dependency array
)

const handleTokenAmountChange = useCallback(
Expand Down Expand Up @@ -405,7 +405,7 @@
const showLimitsCard = !isCryptoWithdraw && (limitsValidation.isBlocking || limitsValidation.isWarning)

return (
<div className="space-y-8 flex min-h-[inherit] flex-col justify-start">
<div className="flex min-h-[inherit] flex-col justify-start gap-8">
<NavHeader
title={isFromSendFlow ? tNav('send') : tNav('withdraw')}
onPrev={() => {
Expand Down
2 changes: 1 addition & 1 deletion src/app/(setup)/setup/finish/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function FinishSetupPageContent() {
description={t('finish.description')}
showBackButton={false}
showSkipButton={false}
contentClassName="flex flex-col items-center justify-center gap-5"
contentClassName="flex flex-col items-center justify-center gap-6"
>
<SignTestTransaction />
</SetupWrapper>
Expand Down
2 changes: 1 addition & 1 deletion src/app/(setup)/setup/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@
return () => {
window.removeEventListener('beforeinstallprompt', handleBeforeInstallPrompt)
}
}, [dispatch, steps, inviteCodeParam, legacyStepParam])

Check warning on line 293 in src/app/(setup)/setup/page.tsx

View workflow job for this annotation

GitHub Actions / eslint

React Hook useEffect has missing dependencies: 'detectedDeviceType' and 'inviteCode'. Either include them or remove the dependency array

useEffect(() => {
if (step) {
Expand All @@ -315,7 +315,7 @@
image={PeanutWavingHello.src}
title={t('existingSession.title')}
description={t('existingSession.description', { username: existingSessionUsername })}
contentClassName="flex flex-col items-center justify-center gap-5"
contentClassName="flex flex-col items-center justify-center gap-6"
>
<div className="flex w-full flex-col gap-3">
<Button shadowSize="4" onClick={handleContinueSession} disabled={isLoggingOut}>
Expand Down
2 changes: 1 addition & 1 deletion src/app/kyc/success/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default function KycSuccessPage() {
<div className="flex h-screen min-h-full w-full flex-col items-center justify-center gap-4">
<Image src={HandThumbsUp} alt="Peanut HandThumbsUp" className="size-34" />
<div className="space-y-2">
<p className="text-body-l font-semibold">{t('successTitle')}</p>
<p className="text-heading-card">{t('successTitle')}</p>
<p className="text-body-s text-foreground-secondary">{t('successCloseWindow')}</p>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/0_Bruddle/DataRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const DataRow = ({
{moreInfoText && (
<div className="relative z-20 flex items-center justify-center px-2">
<Tooltip content={moreInfoText} position="right">
<Icon name="info" size={12} />
<Icon name="info" size={16} />
</Tooltip>
</div>
)}
Expand Down
6 changes: 3 additions & 3 deletions src/components/AddMoney/components/ChainChip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ interface ChainChipProps {

const ChainChip = ({ chainName, chainSymbol, logo, logoClassName }: ChainChipProps) => {
return (
<Card className="flex w-fit flex-row items-center gap-1 rounded-full border-0 bg-transparent p-1 px-1.5">
{chainSymbol && <Image src={chainSymbol} alt={chainName} width={18} height={18} />}
{logo && <Icon name={logo} width={18} height={18} className={logoClassName} />}
<Card className="flex w-fit flex-row items-center gap-1 rounded-full border-0 bg-transparent p-1 px-2">
{chainSymbol && <Image src={chainSymbol} alt={chainName} width={16} height={16} />}
{logo && <Icon name={logo} width={16} height={16} className={logoClassName} />}
<p className="text-body-xs text-foreground-primary">{chainName}</p>
</Card>
)
Expand Down
4 changes: 2 additions & 2 deletions src/components/AddMoney/components/HowToDepositModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ const HowToDepositModal = ({ visible, onClose }: HowToDepositModalProps) => {
onClose={onClose}
title={t('title')}
content={
<div className="flex w-full flex-col gap-5 text-left">
<div className="flex w-full flex-col gap-4 text-left">
<div className="flex flex-col overflow-hidden rounded-sm border border-border-default bg-background-default">
{steps.map((item, index) => (
<div
key={index}
className={`px-4 py-3 ${index !== steps.length - 1 ? 'border-b border-border-default' : ''}`}
>
<p className="text-body-s font-bold">{item.step}</p>
<p className="text-label-l">{item.step}</p>
<p className="text-body-s text-foreground-secondary">{item.text}</p>
</div>
))}
Expand Down
6 changes: 3 additions & 3 deletions src/components/AddMoney/components/InputAmountStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const InputAmountStep = ({
if (currencyData?.isLoading) {
// dev keeps the header mounted so back always works during the load
return (
<div className="space-y-8 flex min-h-[inherit] flex-col justify-start">
<div className="flex min-h-[inherit] flex-col justify-start gap-8">
<NavHeader title={t('title')} onPrev={onBack} />
<Loading variant="mascot" />
</div>
Expand All @@ -83,11 +83,11 @@ const InputAmountStep = ({
: null

return (
<div className="space-y-8 flex min-h-[inherit] flex-col justify-start">
<div className="flex min-h-[inherit] flex-col justify-start gap-8">
<NavHeader title={t('title')} onPrev={onBack} />
<div className="my-auto flex flex-grow flex-col justify-center gap-4 md:my-0">
{maintenanceBanner}
<div className="text-body-s font-bold">{t('howMuchToAdd')}</div>
<div className="text-label-l">{t('howMuchToAdd')}</div>

<AmountInput
initialAmount={tokenAmount}
Expand Down
Loading
Loading