mirror of
https://github.com/vernu/textbee.git
synced 2026-08-01 01:48:17 -04:00
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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
46
web/e2e/mobile-overflow.spec.ts
Normal file
46
web/e2e/mobile-overflow.spec.ts
Normal 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)
|
||||
})
|
||||
})
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user