fix: eliminate mobile horizontal overflow and add scroll-width e2e guard

Root causes of the sideways-scroll blank strip at 375px:
- fixed-width auth cards (w-[400px]/w-[450px]) -> w-full + max-w so they
  shrink on small screens
- min-w-[500px] on the community-links dialog -> removed
- webhooks section header: two w-full buttons in a non-wrapping flex row
  (403px wide) -> stacks on mobile
- devices/api-keys cards blown past the grid track by min-content sizing
  -> min-w-0 max-w-full on the cards
- api-guide SyntaxHighlighter blocks get overflowX auto + maxWidth 100%
- safety net: overflow-x clip on body

Adds e2e/mobile-overflow.spec.ts: at 375px, scrollWidth must not exceed the
viewport on home, messaging, webhooks, account, and login. This guard caught
the webhooks-header and card offenders during the fix.

Build, 25 unit tests, and 11 e2e green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
isra el
2026-07-11 07:27:04 +03:00
parent 79e6dce8f7
commit dda38204fa
11 changed files with 60 additions and 8 deletions

View File

@@ -100,7 +100,7 @@ export default function RequestPasswordResetForm() {
return (
<div className='flex items-center justify-center min-h-screen bg-gray-100 dark:bg-muted'>
<Card className='w-[400px] shadow-lg'>
<Card className='w-full max-w-[400px] shadow-lg'>
<CardHeader className='space-y-1'>
<CardTitle className='text-2xl font-bold text-center'>
Reset your password

View File

@@ -82,7 +82,7 @@ export default function ResetPasswordForm({
return (
<div className='flex items-center justify-center min-h-screen bg-gray-100 dark:bg-muted'>
<Card className='w-[400px] shadow-lg'>
<Card className='w-full max-w-[400px] shadow-lg'>
<CardHeader className='space-y-1'>
<CardTitle className='text-2xl font-bold text-center'>
Reset your password

View File

@@ -22,7 +22,7 @@ export default function LoginPage() {
return (
<div className='flex items-center justify-center min-h-screen bg-gray-100 dark:bg-muted'>
<Card className='w-[400px] shadow-lg'>
<Card className='w-full max-w-[400px] shadow-lg'>
<CardHeader className='space-y-1'>
<CardTitle className='text-2xl font-bold text-center'>
Welcome back

View File

@@ -21,7 +21,7 @@ export default function RegisterPage() {
return (
<div className='flex items-center justify-center min-h-screen bg-gray-100 dark:bg-muted'>
<Card className='w-[450px] shadow-lg'>
<Card className='w-full max-w-[450px] shadow-lg'>
<CardHeader className='space-y-1'>
<CardTitle className='text-2xl font-bold text-center'>
Create an account

View File

@@ -126,7 +126,7 @@ export default function ApiKeys() {
return (
<>
<GenerateApiKey ref={addApiKeyRef} showTrigger={false} />
<Card>
<Card className='min-w-0 max-w-full'>
<CardHeader className='flex flex-row items-center justify-between space-y-0 pb-2'>
<CardTitle className='text-lg'>API Keys</CardTitle>
<div className='flex items-center gap-1'>

View File

@@ -203,7 +203,7 @@ export default function CommunityLinks() {
</Card>
</div>
<Dialog open={socialOpen} onOpenChange={setSocialOpen}>
<DialogContent className='sm:max-w-[600px] min-w-[500px] text-base'>
<DialogContent className='sm:max-w-[600px] text-base'>
<DialogHeader>
<DialogTitle className='text-primary mb-2 text-2xl font-bold'>
Share textbee.dev with Others

View File

@@ -106,7 +106,7 @@ export default function DeviceList() {
return (
<>
<GenerateApiKey ref={addDeviceKeyRef} showTrigger={false} />
<Card>
<Card className='min-w-0 max-w-full'>
<CardHeader className='flex flex-row items-center justify-between space-y-0 pb-2'>
<CardTitle className='text-lg'>Registered Devices</CardTitle>
<Button

View File

@@ -83,7 +83,7 @@ export default function WebhooksSection() {
up to {MAX_WEBHOOKS_PER_USER} webhooks.
</p>
</div>
<div className='flex gap-x-4'>
<div className='flex w-full flex-col gap-2 sm:w-auto sm:flex-row sm:gap-x-4'>
<Button
onClick={handleCreateClick}
disabled={reachedLimit || isLoading}

View File

@@ -353,6 +353,8 @@ print(response.json())`
fontSize: '0.875rem',
lineHeight: '1.5',
backgroundColor: '#1e293b', // slate-800
overflowX: 'auto',
maxWidth: '100%',
}}
>
{data.code}
@@ -391,6 +393,8 @@ print(response.json())`
fontSize: '0.875rem',
lineHeight: '1.5',
backgroundColor: '#1e293b', // slate-800
overflowX: 'auto',
maxWidth: '100%',
}}
>
{endpoint.response.code}

View File

@@ -0,0 +1,46 @@
import { expect, test } from '@playwright/test'
import { authenticate } from './session'
import { mockApi } from './mock-api'
// Regression guard for mobile horizontal overflow: at 375px no page may be
// wider than the viewport (the bug showed a blank strip when scrolling right).
// document.scrollingElement covers the page; body scrollWidth catches clipped
// overflow introduced inside the layout.
const AUTHED_PAGES = [
'/dashboard',
'/dashboard/messaging',
'/dashboard/webhooks',
'/dashboard/account',
]
async function expectNoHorizontalScroll(page: import('@playwright/test').Page) {
const { scrollWidth, innerWidth } = await page.evaluate(() => ({
scrollWidth: document.documentElement.scrollWidth,
innerWidth: window.innerWidth,
}))
expect(scrollWidth, 'page must not scroll sideways at 375px').toBeLessThanOrEqual(
innerWidth
)
}
test.describe('no horizontal overflow at 375px (mocked API)', () => {
test.use({ viewport: { width: 375, height: 800 } })
for (const path of AUTHED_PAGES) {
test(`authed page ${path}`, async ({ page, context }) => {
await authenticate(context)
await mockApi(page)
await page.goto(path)
await page.waitForLoadState('networkidle')
await expectNoHorizontalScroll(page)
})
}
test('login page', async ({ page }) => {
await mockApi(page)
await page.goto('/login')
await page.waitForLoadState('networkidle')
await expectNoHorizontalScroll(page)
})
})

View File

@@ -184,6 +184,8 @@
color: hsl(var(--foreground));
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
/* Safety net: an oversized element must never widen the page sideways. */
overflow-x: clip;
}
}